<?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=Jschleic</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=Jschleic"/>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php/Special:Contributions/Jschleic"/>
		<updated>2026-05-10T12:05:00Z</updated>
		<subtitle>User contributions</subtitle>
		<generator>MediaWiki 1.30.0</generator>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Sortieren&amp;diff=4745</id>
		<title>Sortieren</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Sortieren&amp;diff=4745"/>
				<updated>2010-08-18T15:24:17Z</updated>
		
		<summary type="html">&lt;p&gt;Jschleic: /* Datenspeicherung */ typo&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;----&lt;br /&gt;
== Laufzeitmessung in Python ==&lt;br /&gt;
&lt;br /&gt;
Verwendung der '''timeit-Bibliothek''' für die Hausaufgabe. &lt;br /&gt;
&lt;br /&gt;
* Importiere das timeit-Modul: &amp;lt;tt&amp;gt;import timeit&amp;lt;/tt&amp;gt;&lt;br /&gt;
* Teile den Algorithmus in die Initialisierungen und den Teil, dessen Geschwindigkeit gemessen werden soll. Beide Teile werden in jeweils einen (mehrzeiligen) String eingeschlossen:&lt;br /&gt;
&lt;br /&gt;
  +--------+     +----+            setup = &amp;quot;&amp;quot;&amp;quot;            prog = &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
  |  algo  | --&amp;gt; |init|                +----+                 +----+&lt;br /&gt;
  |        |     +----+                |init|                 |prog|&lt;br /&gt;
  |        |                           +----+                 +----+&lt;br /&gt;
  |        |     +----+             &amp;quot;&amp;quot;&amp;quot;                     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
  |        | --&amp;gt; |prog|            &lt;br /&gt;
  +--------+     +----+            &lt;br /&gt;
&lt;br /&gt;
* aus den beiden Strings wird ein Timeit-Objekt erzeugt: &amp;lt;tt&amp;gt;t = timeit.Timer(prog, setup)&amp;lt;/tt&amp;gt;&lt;br /&gt;
* Frage: Wie oft soll die Algorithmik wiederholt werden&lt;br /&gt;
:z.B. N = 1000&lt;br /&gt;
* Zeit in Sekunden für N Durchläufe: &amp;lt;tt&amp;gt;K = t.timeit(N)&amp;lt;/tt&amp;gt;&lt;br /&gt;
:Zeit für 1 Durchlauf: K/N&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
3.Stunde am 16.04.2008&lt;br /&gt;
&lt;br /&gt;
==Sortierverfahren==&lt;br /&gt;
&lt;br /&gt;
=== Motivation ===&lt;br /&gt;
'''Def:''' &lt;br /&gt;
Ein Sortierverfahren ist ein Algorithmus, der dazu dient, eine Liste von Elementen zu sortieren.&lt;br /&gt;
* Literatur, siehe Sortierverfahren; Bubblesort 1956, Quicksort 1962. Librarysort 2004  &lt;br /&gt;
&lt;br /&gt;
'''Anwendungen'''&lt;br /&gt;
* Sortierte Daten sind häufig Vorbedingungen für Suchverfahren (Speziell für effiziente Suchalgorithmen mit Komplexität &amp;lt;math&amp;gt;\mathcal{O}(log(N))&amp;lt;/math&amp;gt;)&lt;br /&gt;
* Darstellung von Daten gemäß menschlicher Wahrnehmung &lt;br /&gt;
* Aus programmiertechnischer Anwendungssicht hat das Sortierproblem allerdings heute an Relevanz verloren da&lt;br /&gt;
** gängige Programmiersprachen heute typunabhängige Algorithmen zur Verfügung stellen. Der Programmierer braucht sich deshalb in den meisten Fällen nicht mehr um die Implementierung von Sortieralgorithmen zu kümmern. In C/C++ sorgen dafür beispielsweise Methoden aus der [http://de.wikipedia.org/wiki/Standard_Template_Library STL].&lt;br /&gt;
** Festplatten / Hauptspeicher heute weniger limitierenden Charakter haben, so dass Standardsortierverfahren meist ausreichen, während komplizierte, speicher-sparende Sortieralgorithmen nur noch selten benötigt werden.&lt;br /&gt;
* Die Kenntnis grundlegender Sortieralgorithmen ist trotzdem immer noch nötig: Einerseits kann man vorgefertigte Bausteine nur dann optimal einsetzen, wenn man weiß, was hinter den Kulissen passiert und andererseits verdeutlicht gerade das Sortierproblem wichtige Prinzipien der Algorithmenentwicklung und -analyse in sehr anschaulicher Form.&lt;br /&gt;
&lt;br /&gt;
=== Vorraussetzungen/ Spielregeln ===&lt;br /&gt;
&lt;br /&gt;
==== Mengentheoretische  Anforderungen====&lt;br /&gt;
Definition Totale Ordnung/ Total gordnete Menge:&lt;br /&gt;
Eine Totale Ordnung / Total geordnete Menge ist eine binäre Relation    &lt;br /&gt;
&amp;lt;math&amp;gt;R \subseteq M \times M&amp;lt;/math&amp;gt; über einer Menge &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt;, die transitiv, antisymmetrisch und total ist.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; sei  dargestellt als infix Notation &amp;lt;math&amp;gt;\le &amp;lt;/math&amp;gt; dann, falls M total geordnet, gilt &lt;br /&gt;
&amp;lt;math&amp;gt; \forall a,b,c \ \epsilon M &amp;lt;/math&amp;gt; &amp;lt;br/&amp;gt;&lt;br /&gt;
(1) &amp;lt;math&amp;gt;a \le b \wedge b \le a \Rightarrow a=b &amp;lt;/math&amp;gt; (antisymmetrisch)&amp;lt;br/&amp;gt;&lt;br /&gt;
(2) &amp;lt;math&amp;gt;a \le b \wedge b \le c \Rightarrow a \le c &amp;lt;/math&amp;gt; (transitiv)&amp;lt;br/&amp;gt;&lt;br /&gt;
(3) &amp;lt;math&amp;gt;a \le b \vee b \le a &amp;lt;/math&amp;gt; (total) &amp;lt;br/&amp;gt;&lt;br /&gt;
Bemerkung: aus (3) folgt &amp;lt;math&amp;gt; a \le a &amp;lt;/math&amp;gt; (reflexiv) &amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
''Hab in der Wiki eine gute Seite dazu gefunden [http://de.wikipedia.org/wiki/Ordnungsrelation'' Ordnungsrelation]&lt;br /&gt;
&lt;br /&gt;
==== Datenspeicherung ====&lt;br /&gt;
&lt;br /&gt;
Die Daten liegen typischerweise in Form von Arrays oder verketteten Listen vor. Je nach Datenstruktur sind andere Sortieralgorithmen am besten geeignet. &lt;br /&gt;
;Array:&lt;br /&gt;
        +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
        |///|   |   |   |   |   |   |   |///|  &lt;br /&gt;
        +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
       \________________  ____________________/&lt;br /&gt;
                        \/&lt;br /&gt;
                        N&lt;br /&gt;
Datenelemente können über Indexoperation a[i] gelesen, überschrieben und miteinander vertauscht werden. Vorteil: Die Zugriffsreihenfolge auf die Datenelemente ist beliebig. Nachteil: Einfügen oder Löschen von Elementen aus dem Array ist relativ aufwändig.&lt;br /&gt;
&lt;br /&gt;
;Vekettete Liste:  &lt;br /&gt;
        +---+     +---+     +---+&lt;br /&gt;
        |   | --&amp;gt; |   | --&amp;gt; |   | --&amp;gt; Ende &lt;br /&gt;
        +---+     +---+     +---+&lt;br /&gt;
   &lt;br /&gt;
Jeder Knoten der Liste enthält ein Datenelement und einen Zeiger auf den nächsten Knoten. Vorteil: Einfügen und Löschen von Elementen ist effizient möglich. Nachteil: effizienter Zugriff nur auf den Nachfolger eines gegebenen Elements, d.h. Zugriffsreihenfolge ist nicht beliebig.&lt;br /&gt;
&lt;br /&gt;
==== Stabilität ====&lt;br /&gt;
&lt;br /&gt;
Ein Sortierverfahren heißt ''stabil'' falls die relative Reihenfolge gleicher Schlüssel durch die Sortierung nicht verändert wird.&lt;br /&gt;
&lt;br /&gt;
Beispiel: Sortiere eine Liste von Paaren &amp;lt;tt&amp;gt;[(3,7), (4,2), (4,1), (2,2), (2,8)]&amp;lt;/tt&amp;gt;, wobei die Reihenfolge nur durch das erste Element (Schlüsselelement) jeden Paares festgelegt wird.&lt;br /&gt;
Dann erzeugt ein stabiles Sortierverfahren die Ausgabe&lt;br /&gt;
 [(2,2), (2,8), (3,7), (4,2), (4,1)]&lt;br /&gt;
während die Ausgabe&lt;br /&gt;
 [(2,2), (2,8), (3,7), (4,1), (4,2)]&lt;br /&gt;
nicht stabil ist (die Paare &amp;lt;tt&amp;gt;(4,1), (4,2)&amp;lt;/tt&amp;gt; sind vertauscht).&lt;br /&gt;
&lt;br /&gt;
==== Charakterisierung der Effizienz von Algorithmen ====&lt;br /&gt;
  &lt;br /&gt;
:(a) Komplexität O(1), O(n), etc. wird in Kapitel [[Effizienz]] erklärt.&lt;br /&gt;
:(b) Zählen der notwendigen Vergleiche&lt;br /&gt;
:(c) Messen der Laufzeit mit 'timeit' (auf identischen Daten)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Rekursive Beziehungen'''&lt;br /&gt;
zerlegt die ursprünglichen Probleme in kleinere Probleme und wendet den Algorithmus auf die kleineren Probleme an; daraufhin werden die Teilprobleme zur Lösung des Gesamtproblems verwendet. &lt;br /&gt;
d.h. Laufzeit (operativer Vergleich) für N Eingaben hängt von der Laufzeit der Eingaben für die Teilprobleme &lt;br /&gt;
&lt;br /&gt;
'''Aufwand'''&lt;br /&gt;
&lt;br /&gt;
(i) rekursives/ lineares Durchlaufen der Eingabedaten, Bearbeitung einzelner Elemente&lt;br /&gt;
&lt;br /&gt;
 C(N)= C(N-1)+ N ;  N&amp;gt;1, C(1)= 1             +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
     = C(N-2) +(N-1)+ N                      | 7  | 3 | 2 | 5 | 6 | 8 | 1 | 4 | 2 |  &lt;br /&gt;
     = C(N-3) + (N-2) + (N-1) + N            +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
     = ...                                         ________________________/&lt;br /&gt;
     = C(1) + 2+...+(N-1) +N                     /&lt;br /&gt;
                                               +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
        N(N+1)   N²                            | 1 | 3 | 2 | 5 | 6 | 8 | 7 | 4 | 2 |  &lt;br /&gt;
      = -----  ~ --                            +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
          2       2                   &lt;br /&gt;
            &lt;br /&gt;
                                                      &lt;br /&gt;
                                               &lt;br /&gt;
   &lt;br /&gt;
&lt;br /&gt;
(ii) rekursives halbieren der Menge der Eingabedaten&lt;br /&gt;
    &lt;br /&gt;
 C(N)= C(N/2)+1 ; N&amp;gt;1, C(1)=0&lt;br /&gt;
 Aus Gründen der Einfachheit sei N  = 2^n&lt;br /&gt;
 &lt;br /&gt;
 C(N)= C(2^n)= C(&amp;lt;math&amp;gt;2^{n-1}&amp;lt;/math&amp;gt;) + 1       &lt;br /&gt;
                     &lt;br /&gt;
              = C(&amp;lt;math&amp;gt;2^{n-1}&amp;lt;/math&amp;gt;) + 1 + 1   &lt;br /&gt;
              = ...                    &lt;br /&gt;
                                       &lt;br /&gt;
              = C(&amp;lt;math&amp;gt;2^0&amp;lt;/math&amp;gt;) + n               &lt;br /&gt;
              = n                    &lt;br /&gt;
              = &amp;lt;math&amp;gt;log_2 N&amp;lt;/math&amp;gt;                 &lt;br /&gt;
 +---+---+---+---+-|-+---+---+---+---+ &lt;br /&gt;
 |   |    |   |   |   |   |    |   |   |&lt;br /&gt;
 +---+---+---+---+-|-+---+---+---+---+&lt;br /&gt;
 +---+---+---+---+&lt;br /&gt;
 |   |    |   |   |&lt;br /&gt;
 +---+---+---+---+&lt;br /&gt;
 +---+---+        +---+&lt;br /&gt;
 |   |    |  -&amp;gt;   |   |&lt;br /&gt;
 +---+---+        +---+&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
(iii) rekursives halbieren, lineare Bearbeitung, jedes Elements&lt;br /&gt;
  &lt;br /&gt;
 C(N)= 2C(N/2)+ N; N&amp;gt;1, C(1)= 0&lt;br /&gt;
 Sei N= &amp;lt;math&amp;gt;2^n&amp;lt;/math&amp;gt;&lt;br /&gt;
 C(N)= C(&amp;lt;math&amp;gt;2^n&amp;lt;/math&amp;gt;)= 2C (&amp;lt;math&amp;gt;2^{n-1}&amp;lt;/math&amp;gt;)+ &amp;lt;math&amp;gt;2^n&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;=&amp;gt; &amp;lt;math&amp;gt; \cfrac{C(2^n)}{2^n}&amp;lt;/math&amp;gt; = &amp;lt;math&amp;gt; \cfrac{2C(2^{n-1})}{2^{n-1}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 = &amp;lt;math&amp;gt; \cfrac{2C(2^{n-2})+2^{n-1}}{2^{n-1}}+1&amp;lt;/math&amp;gt;&lt;br /&gt;
 = &amp;lt;math&amp;gt; \cfrac{2C(2^{n-2})}{2^{n-2}}+1 +1&amp;lt;/math&amp;gt;&lt;br /&gt;
 =...&lt;br /&gt;
 = n&lt;br /&gt;
&amp;lt;=&amp;gt; C(&amp;lt;math&amp;gt;2^n&amp;lt;/math&amp;gt;)= &amp;lt;math&amp;gt;2^n&amp;lt;/math&amp;gt; * n&lt;br /&gt;
&amp;lt;=&amp;gt; C= N log&amp;lt;math&amp;gt;_2&amp;lt;/math&amp;gt;N&lt;br /&gt;
&lt;br /&gt;
==Selection Sort==&lt;br /&gt;
&lt;br /&gt;
===Algorithmus===&lt;br /&gt;
&lt;br /&gt;
 array = [...]  # zu sortierendes Array&lt;br /&gt;
 &lt;br /&gt;
 for i in range(len(array)-1):&lt;br /&gt;
    min = i&lt;br /&gt;
    for j in range(i+1, len(array)):&lt;br /&gt;
       if a[j]&amp;lt; a[min]:&lt;br /&gt;
           min = j&lt;br /&gt;
    a[i], a[min] = a[min], a[i]  # Vertausche a[i] mit dem kleinsten rechts befindlichen Element&lt;br /&gt;
                                 # Elemente links von a[i] und a[i] selbst befinden sich nun in ihrer endgültigen Position&lt;br /&gt;
&lt;br /&gt;
Beispiel: Sortieren der Liste &amp;lt;tt&amp;gt;[S,O,R,T,I,N,G]&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
 erste Iteration der äußeren Schleife, Zustand ''vor'' dem Vertauschen:&lt;br /&gt;
  i=0                     min&lt;br /&gt;
 +---+---+---+---+---+---+---+&lt;br /&gt;
 | S | O | R | T | I | N | G |&lt;br /&gt;
 +---+---+---+---+---+---+---+&lt;br /&gt;
 &lt;br /&gt;
 erste Iteration der äußeren Schleife, Zustand ''nach'' dem Vertauschen:&lt;br /&gt;
 +---|---+---+---+---+---+---+&lt;br /&gt;
 | G | O | R | T | I | N | S |&lt;br /&gt;
 +---|---+---+---+---+---+---+&lt;br /&gt;
 &lt;br /&gt;
 zweite Iteration der äußeren Schleife:&lt;br /&gt;
      i=1         min&lt;br /&gt;
 +---|---+---+---+---+---+---+&lt;br /&gt;
 | G | O | R | T | I | N | S |&lt;br /&gt;
 +---|---+---+---+---+---+---+&lt;br /&gt;
 &lt;br /&gt;
 weitere Iterationen:&lt;br /&gt;
          i=2         min&lt;br /&gt;
 +---+---|---+---+---+---+---+&lt;br /&gt;
 | G | I | R | T | O | N | S |&lt;br /&gt;
 +---+---|---+---+---+---+---+ &lt;br /&gt;
 &lt;br /&gt;
              i=3 min&lt;br /&gt;
 +---+---+---|---+---+---+---+&lt;br /&gt;
 | G | I | N | T | O | R | S |&lt;br /&gt;
 +---+---+---|---+---+---+---+ &lt;br /&gt;
 &lt;br /&gt;
                  i=4 min&lt;br /&gt;
 +---+---+---+---+---+---+---+&lt;br /&gt;
 | G | I | N | O | T | R | S |&lt;br /&gt;
 +---+---+---+---+---+---+---+  &lt;br /&gt;
 ...&lt;br /&gt;
&lt;br /&gt;
===Laufzeit===&lt;br /&gt;
&lt;br /&gt;
Da in jeder Iteration der ''inneren'' Schleife ein Vergleich &amp;lt;tt&amp;gt;a[j]&amp;lt; a[min]&amp;lt;/tt&amp;gt; durchgeführt wird, ist die Anzahl der Vergleiche ein gutes Maß für den Aufwand des Algorithmus und damit für die Laufzeit. Sei C(N) die Anzahl der notwendigen Vergleiche, um ein Array der Größe N zu sortieren. Die Arbeitsweise des Algorithmus kann dann so beschrieben werden: Führe N-1 Vergleiche aus, bringe das kleinste Element an die erste Stelle, und fahre mit dem Sortieren des Rest-Arrays (Größe N-1) rechts des ersten Elements fort. Dafür sind nach Definition noch C(N-1) Vergleiche nötig. Es gilt also:&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = C(N-1) + (N-1)&amp;lt;/math&amp;gt;&lt;br /&gt;
C(N-1) können wir nach der gleichen Formel einsetzen, und erhalten:&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = C(N-2) + (N-2) + (N-1)&amp;lt;/math&amp;gt;&lt;br /&gt;
Wir können in dieser Weise weiter fortfahren. Bei C(1) wird das Einsetzen beendet, denn für ein Array der Länge 1 sind keine Vergleiche mehr nötig, also C(1) = 0. Wir erhalten somit&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = C(N-3) + (N-3) + (N-2) + (N-1)&amp;lt;/math&amp;gt;&lt;br /&gt;
:::&amp;lt;math&amp;gt;...&amp;lt;/math&amp;gt;&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = C(1) + 1 + 2 + ...+ (N-2)+ (N-1)&amp;lt;/math&amp;gt;&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = 0 + 1 + 2 + ...+ (N-2)+ (N-1)&amp;lt;/math&amp;gt;&lt;br /&gt;
Nach der Gaußschen Summenformel ist dies&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = \frac {(N-1)N}{2}\approx \cfrac {(N^2)}{2}&amp;lt;/math&amp;gt; (für große N).&lt;br /&gt;
&lt;br /&gt;
In jedem Durchlauf der äußeren Schleife werden außerdem zwei Elemente ausgetauscht. Es gilt für die Anzahl der Austauschoperationen&lt;br /&gt;
:::&amp;lt;math&amp;gt;A(N)= N-1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Stabilität===&lt;br /&gt;
&lt;br /&gt;
Selection Sort ist stabil, wenn die Vergleiche durch &amp;lt;tt&amp;gt;a[j] &amp;lt; a[min]&amp;lt;/tt&amp;gt; erfolgen, weil dann immer das erste Element mit einem gegebenen Schlüssel als erster nach vorn gebracht wird. Bei Vergleichen &amp;lt;tt&amp;gt;a[j] &amp;lt;= a[min]&amp;lt;/tt&amp;gt; wird hingegen das letzte Element zuerst nach vorn gebracht, somit ist Selection Sort dann nicht stabil.&lt;br /&gt;
&lt;br /&gt;
==Insertion Sort==&lt;br /&gt;
&lt;br /&gt;
* wird in der Übungsgruppe behandelt, siehe auch in der [http://de.wikipedia.org/wiki/Insertionsort WikiPedia]&lt;br /&gt;
* Erweiterung: [http://en.wikipedia.org/wiki/Shell_sort Shell sort]&lt;br /&gt;
&lt;br /&gt;
== Mergesort ==&lt;br /&gt;
&lt;br /&gt;
===Algorithmus ===&lt;br /&gt;
&lt;br /&gt;
Zugrunde liegende Idee: &lt;br /&gt;
* Zerlege das Problem in zwei möglichst gleich große Teilprobleme (&amp;quot;Teile und herrsche&amp;quot;-Prinzip -- divide and conquer)&lt;br /&gt;
* Löse die Teilprobleme rekursiv&lt;br /&gt;
* Führe die Teillösungen über Mischen (merging) in richtig sortierter Weise zusammen.&lt;br /&gt;
Der Algorithmus besteht somit aus zwei Teilen&lt;br /&gt;
&lt;br /&gt;
====Zusammenführen -- merge====&lt;br /&gt;
&lt;br /&gt;
a und b sind zwei sortierte Listen, die in eine sortierte Ergebnisliste kombiniert werden.&lt;br /&gt;
&lt;br /&gt;
 def merge(a,b):&lt;br /&gt;
     c = []   # zunächst leere Ergebnisliste &lt;br /&gt;
     i, j = 0, 0&lt;br /&gt;
     while i &amp;lt; len(a) and j &amp;lt; len(b):&lt;br /&gt;
         # wähle des kleinste der noch nicht angefügten Elemente&lt;br /&gt;
         if a[i] &amp;lt;= b[j]:&lt;br /&gt;
              c.append(a[i])&lt;br /&gt;
              i += 1&lt;br /&gt;
         else:&lt;br /&gt;
              c.append(b[j])&lt;br /&gt;
              j += 1&lt;br /&gt;
    # eine Liste ist jetzt aufgebraucht =&amp;gt; der Rest der anderen wird einfach an c angehängt&lt;br /&gt;
    if i &amp;lt; len(a):&lt;br /&gt;
        c += a[i:]&lt;br /&gt;
    else:&lt;br /&gt;
        c += b[j:]&lt;br /&gt;
    return c&lt;br /&gt;
&lt;br /&gt;
====rekursives Sortieren====&lt;br /&gt;
&lt;br /&gt;
 def mergeSort(a):  # a ist das zu sortierende Array&lt;br /&gt;
     if len(a) &amp;lt;= 1:&lt;br /&gt;
         return a   # Rekursionsabschluß: leere Arrays und Arrays mit einem Element müssen nicht sortiert werden&lt;br /&gt;
     else:&lt;br /&gt;
         left  = a[:len(a)/2]   # linkes Teilarray&lt;br /&gt;
         right = a[len(a)/2:]   # rechtes Teilarray&lt;br /&gt;
         leftSorted  = mergeSort(left)  # rekursives Sortieren der Teilarrays&lt;br /&gt;
         rightSorted = mergeSort(right) # ...&lt;br /&gt;
         return merge(leftSorted, rightSorted)  # Zusammenführen der Teilarrays&lt;br /&gt;
&lt;br /&gt;
Bei der Sortierung mit Mergesort wird das Array immer in zwei Teile geteilt. → Es entsteht ein Binärbaum der Tiefe &amp;lt;math&amp;gt;\log_2 N&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Beispiel: Sortieren der Liste &amp;lt;tt&amp;gt;[S,O,R,T,I,N,G]&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Der Algorithmus läuft in der folgenden Skizze zunächst rekursiv von unten nach oben (Zerlegen in Teillisten), danach werden die sortierten Teillisten von oben nach unten zusammengeführt (diese sortierten Teillisten sind in der Skizze dargestellt).&lt;br /&gt;
&lt;br /&gt;
 Schritt 0:&lt;br /&gt;
  S 0 R T I N G     S    O R    T I    N     G    #Arraylänge: N/8    Vergleiche: 0&lt;br /&gt;
 Schritt 1:          \  /   \  /   \  /     /&lt;br /&gt;
  OS RT IN G          OS     RT     IN     /      #Arraylänge: N/4    Vergleiche: 3 * 2 = 6&lt;br /&gt;
 Schritt 2:             \    /        \   /       &lt;br /&gt;
  ORST GIN               ORST          GIN        #Arraylänge: N/2    Vergleiche: 4 + 3 = 7&lt;br /&gt;
                            \         /&lt;br /&gt;
 Schritt3:                   \       /&lt;br /&gt;
  GINORST                     GINORST             #Arraylänge: N      Vergleiche: N     = 7&lt;br /&gt;
&lt;br /&gt;
===Laufzeit ===&lt;br /&gt;
&lt;br /&gt;
Man erkennt an der Skizze, dass der Rekursionsbaum für ein Array der Länge N die Tiefe log N hat. Auf jeder Ebene werden weniger als N Vergleiche ausgeführt, so dass insgesamt weniger als N*log N Vergleiche benötigt werden. Dies ist natürlich wesentlich effizienter als die (N-1)*N/2 Vergleiche von Selection Sort. Mathematisch exakt kann man die Anzahl der Vergleiche durch die folgende Rekursionsformel berechnen:&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = C(\lfloor N/2\rfloor) + C(\lceil N/2\rceil) + N&amp;lt;/math&amp;gt;&lt;br /&gt;
Der Aufwand ergibt sich aus dem Aufwand für die beiden Teilprobleme plus dem Aufwand für N Vergleiche beim Zusammenführen der sortierten Teillisten. Dabei stehen die Zeichen &amp;lt;math&amp;gt;\lfloor \rfloor&amp;lt;/math&amp;gt; und &amp;lt;math&amp;gt;\lceil \rceil&amp;lt;/math&amp;gt; für abrunden bzw. aufrunden, weil ein Problem mit ungeradem N nicht in zwei exakt gkeiche Teile geteilt werden kann. Um diese Komplikation zu vermeiden, beschränken wir uns im folgenden auf den Fall &amp;lt;math&amp;gt;N = 2^n&amp;lt;/math&amp;gt; (mit etwas höherem Aufwand kann man zeigen, dass diese Einschränkung nicht notwendig ist und die Resultate für alle N gelten). Die vereinfachte Aufwandsformel lautet:&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = 2 C(N/2) + N&amp;lt;/math&amp;gt;&lt;br /&gt;
Durch Einsetzen der Formel für N/2 erhalten wir:&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = 2 (2 C(N/4) + N/2) + N = 4 C(N/4) + N + N&amp;lt;/math&amp;gt;&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = 4 (2 C(N/8) + N/4) + N + N = 8 C(N/8) + N + N + N&amp;lt;/math&amp;gt;&lt;br /&gt;
:::&amp;lt;math&amp;gt;...&amp;lt;/math&amp;gt;&lt;br /&gt;
Die Rekursion endet, weil für ein Array der Größe &amp;lt;math&amp;gt;N=1&amp;lt;/math&amp;gt; keine Vergleiche mehr benötigt werden, also &amp;lt;math&amp;gt;C(1) = 0&amp;lt;/math&amp;gt; gilt. Mit &amp;lt;math&amp;gt;N=2^n&amp;lt;/math&amp;gt; ist dies aber gerade nach &amp;lt;math&amp;gt;n = \log_2 N&amp;lt;/math&amp;gt; Zerlegungen der Fall. Merge Sort benötigt also&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = N + ... + N = n \cdot N = N\cdot \log_2 N&amp;lt;/math&amp;gt;&lt;br /&gt;
Vergleiche.&lt;br /&gt;
&lt;br /&gt;
===Weitere Eigenschaften von MergeSort ===&lt;br /&gt;
&lt;br /&gt;
* Mergesort ist '''stabil''': wegen des Vergleichs &amp;lt;tt&amp;gt;a[i] &amp;lt;= b[j]&amp;lt;/tt&amp;gt; wird die Position gleicher Schlüssel im Algorithmus &amp;lt;tt&amp;gt;merge(a,b)&amp;lt;/tt&amp;gt; nicht verändert -- bei gleichem Schlüssel hat, wie gefordert, das linke Element Vorrang.&lt;br /&gt;
* Mergesort ist '''unempfindlich gegenüber der ursprünglichen Reihenfolge der Eingabedaten'''. Grund dafür ist&lt;br /&gt;
** die vollständige Aufteilung des Ausgangsarrays in Arrays der Länge 1 und&lt;br /&gt;
** dass &amp;lt;tt&amp;gt;merge(a,b)&amp;lt;/tt&amp;gt; die Vorsortierung nicht ausnutzt, d.h. die Komplexität von &amp;lt;tt&amp;gt;merge(a,b)&amp;lt;/tt&amp;gt; ist sortierungsunabhängig.&lt;br /&gt;
* Diese Eigenschaft kann unerwünscht sein, wenn ein Teil des Arrays oder gar das ganze Array schon sortiert ist. Es wird nämlich in jedem Fall das ganze Array neu sortiert.&lt;br /&gt;
* Merge Sort eignet sich für das Sortieren von '''verketteten Listen''', weil die Listenelemente stets von vorn nach hinten durchlaufen werden. In diesem Fall muss &amp;lt;tt&amp;gt;merge(a, b)&amp;lt;/tt&amp;gt; keine neue Liste &amp;lt;tt&amp;gt;c&amp;lt;/tt&amp;gt; für das Ergebnis anlegen, sondern kann einfach die Verkettung der Listenelemente von &amp;lt;tt&amp;gt;a&amp;lt;/tt&amp;gt; und &amp;lt;tt&amp;gt;b&amp;lt;/tt&amp;gt; entsprechend anpassen. In diesem Sinne arbeitet Merge Sort auf verketten Listen &amp;quot;in place&amp;quot;, d.h. es wird kein zusätzlicher Speicher benötigt.&lt;br /&gt;
* Im Gegensatz dazu benötigt &amp;lt;tt&amp;gt;merge(a,b)&amp;lt;/tt&amp;gt; zusätzlichen Speicher für das Ergebnis &amp;lt;tt&amp;gt;c&amp;lt;/tt&amp;gt;, wenn die Daten in einem Array gegeben sind.&lt;br /&gt;
&lt;br /&gt;
== Quicksort ==&lt;br /&gt;
&lt;br /&gt;
* Quicksort wurde in den 60er Jahren von Charles Antony Richard Hoare [http://de.wikipedia.org/wiki/C._A._R._Hoare] entwickelt. Es gibt viele Implementierungen von Quicksort, vgl. [http://de.wikipedia.org/wiki/Quicksort].&lt;br /&gt;
* Dieser Algorithmus gehört zu den &amp;quot;Teile und herrsche&amp;quot;-Algorithmen (divide-and-conquer) und ist der Standardalgorithmus für Sortieren.&lt;br /&gt;
* Im Gegensatz zu Merge Sort wird das Problem aber nicht immer in zwei fast gleich große Teilprobleme zerlegt. Dadurch vermeidet man, dass zusätzlicher Speicher benötigt wird (Quick Sort arbeitet auch für Arrays &amp;quot;in place&amp;quot;). Allerdings erkauft man sich dies dadurch, dass Quick Sort bei ungünstigen Eingaben (die Bedeutung von &amp;quot;ungünstig&amp;quot; ist je nach Implementation verschieden) nicht effizient arbeitet. Da solche Eingaben jedoch in der Praxis fast nie vorkommen, tut dies der Beliebtheit von Quicksort keinen Abbruch.&lt;br /&gt;
&lt;br /&gt;
=== Algorithmus===&lt;br /&gt;
&lt;br /&gt;
Wie Merge Sort arbeitet Quick Sort rekursiv. Hier werden die Daten allerdings zuerst vorbereitet (in der Funktion &amp;lt;tt&amp;gt;partition&amp;lt;/tt&amp;gt;), und danach erfolgt der rekursive Aufruf:&lt;br /&gt;
&lt;br /&gt;
 def quicksort(a, l, r): &lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;a ist das zu sortierende Array, &lt;br /&gt;
        l und r sind die linke und rechte Grenze des zu sortierenden Bereichs&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
      if r &amp;gt; l:                     # Rekursionsabschluss: wenn r &amp;lt;= l, ist der Bereich leer und muss nicht mehr sortiert werden&lt;br /&gt;
          i = partition(a, l, r)    # i ist der Index des sog. Pivot-Elements (s. u.)&lt;br /&gt;
          quicksort(a, l, i-1)      # rekursives Sortieren der beiden Teilarrays&lt;br /&gt;
          quicksort(a, i+1, r)      # ...&lt;br /&gt;
&lt;br /&gt;
Der Schlüssel des Algorithmus ist offensichtlich die Funktion &amp;lt;tt&amp;gt;partition&amp;lt;/tt&amp;gt;. Diese wählt ein Element des Arrays aus (das Pivot-Element) und bringt es an die richtige Stelle (also an den Index &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt;, der von &amp;lt;tt&amp;gt;partition&amp;lt;/tt&amp;gt; zurückgegeben wird). Ausserdem stellt sie sicher, dass alle Elemente in der linken Teilliste (Index &amp;lt; &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt;) kleiner als &amp;lt;tt&amp;gt;a[i]&amp;lt;/tt&amp;gt;, und alle Elemente in der rechten Teilliste größer also &amp;lt;tt&amp;gt;a[i]&amp;lt;/tt&amp;gt; sind:&lt;br /&gt;
# &amp;lt;math&amp;gt;a[i]&amp;lt;/math&amp;gt; ist sortiert, d.h. dieses Element ist am endgültigen Platz.&lt;br /&gt;
# &amp;lt;math&amp;gt;\forall x \in \left\{ a \left[ l \right] , ... a \left[ i-1 \right] \right\} : x \leq a \left[ i \right]&amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt;\forall x \in \left\{ a \left[ i+1 \right], ... a \left[ r \right] \right\} : x \geq a \left[ i \right]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
          l                               r&lt;br /&gt;
        +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 Array: |   |   |   |   |\\\|   |   |   |   |&lt;br /&gt;
        +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
         \______  _____/  i  \______  _____/ &lt;br /&gt;
                \/                  \/&lt;br /&gt;
             &amp;lt;=a[i]               &amp;gt;=a[i]             (a[i] ist das Pivot-Element)&lt;br /&gt;
&lt;br /&gt;
Die Position von &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt; richtet sich also offensichtlich danach, wie viele Elemente im Bereich &amp;lt;tt&amp;gt;l&amp;lt;/tt&amp;gt; bis &amp;lt;tt&amp;gt;r&amp;lt;/tt&amp;gt; kleiner bzw. größer als das gewählte Pivot-Element sind. Der Wahl eines guten Pivot-Elements kommt demnach eine große Bedeutung zu (s.u.). &lt;br /&gt;
&lt;br /&gt;
In der einfachsten Version wird &amp;lt;tt&amp;gt;partition&amp;lt;/tt&amp;gt; wie folgt definiert:&lt;br /&gt;
&lt;br /&gt;
 def partition(a, l, r):&lt;br /&gt;
     pivot = a[r]     # Pivot-Element. Hier wird willkürlich das letzte Element verwendet.&lt;br /&gt;
     i  = l           # i und j sind Laufvariablen&lt;br /&gt;
     j  = r - 1&lt;br /&gt;
 &lt;br /&gt;
     while True:&lt;br /&gt;
         while a[i] &amp;lt;= pivot and i &amp;lt; r:&lt;br /&gt;
             i += 1               # finde von links das erste Element &amp;gt; pivot&lt;br /&gt;
         while a[j] &amp;gt;= pivot and j &amp;gt; l:&lt;br /&gt;
             j -= 1               # finde von rechts den ersten Eintrag &amp;lt;= pivot&lt;br /&gt;
         if i &amp;gt;= j: break         # keine weiteren Elemente zum Tauschen =&amp;gt; Schleife beenden      &lt;br /&gt;
         a[i], a[j] = a[j], a[i]  # a[i] und a[j] sind beide auf der falschen Seite des Pivot =&amp;gt; vertausche sie&lt;br /&gt;
     if a[i] &amp;gt; pivot:&lt;br /&gt;
         a[i], a[r] = a[r], a[i]&lt;br /&gt;
     return i&lt;br /&gt;
&lt;br /&gt;
Die folgende Skizze verdeutlicht das Austauschen&lt;br /&gt;
&lt;br /&gt;
                                          p&lt;br /&gt;
        +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 Array: |   |   |   |   |   |   |   |   |\\\|&lt;br /&gt;
        +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
        ------&amp;gt; a[i]&amp;gt;p          a[j]&amp;lt;p &amp;lt;-----&lt;br /&gt;
                  |               |&lt;br /&gt;
                  +---------------+&lt;br /&gt;
       Diese zwei Elemente werden ausgetauscht.&lt;br /&gt;
 &lt;br /&gt;
Dies wird wiederholt, bis sich die Zeiger treffen oder einander überholt haben. Am Schluss wird das Pivot-Element an die richtige Stelle verschoben:&lt;br /&gt;
 &lt;br /&gt;
                          p&lt;br /&gt;
        +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 Array: |   |   |   |   |\\\|   |   |   |   |&lt;br /&gt;
        +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
                          i&lt;br /&gt;
        -----------------&amp;gt; &amp;lt;-----------------&lt;br /&gt;
  &lt;br /&gt;
Beispiel: Partitionieren des Arrays &amp;lt;tt&amp;gt;[A,S,O,R,T,I,N,G,E,X,A,M,P,L,E]&amp;lt;/tt&amp;gt; mit Pivot 'E'.&lt;br /&gt;
&lt;br /&gt;
  l,i --&amp;gt;                                          &amp;lt;-- j   r&lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 | A | S | O | R | T | I | N | G | E | X | A | M | P | L | E |&lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 &lt;br /&gt;
       i &amp;lt;--------- Vertauschen ---------&amp;gt; j               r&lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 | A | S | O | R | T | I | N | G | E | X | A | M | P | L | E |&lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 &lt;br /&gt;
           i &amp;lt;-------------------&amp;gt; j                       r&lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 | A | A | O | R | T | I | N | G | E | X | S | M | P | L | E |&lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 &lt;br /&gt;
           j   i                                           r&lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 | A | A | E | R | T | I | N | G | O | X | S | M | P | L | E |   --&amp;gt; Hier wird die &lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+       Schleife verlassen.&lt;br /&gt;
 &lt;br /&gt;
           j   i &amp;lt;---------------------------------------&amp;gt; r&lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 | A | A | E | R | T | I | N | G | O | X | S | M | P | L | E |   &lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 &lt;br /&gt;
               i                                           r&lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 | A | A | E | E | T | I | N | G | O | X | S | M | P | L | R |   --&amp;gt; Hier wird partition() beendet.&lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Weitere ausführliche Erklärungen der Implementation findet man bei Sedgewick.&lt;br /&gt;
&lt;br /&gt;
=== Laufzeit===&lt;br /&gt;
&lt;br /&gt;
Wir müssen hier den schlechtesten und den typischen Fall unterscheiden. Der schlechteste Fall tritt ein, wenn das Array bereits sortiert ist. Dann ist das Pivot-Element immer bereits am richtigen Platz, so dass &amp;lt;tt&amp;gt;partition(a, l, r)&amp;lt;/tt&amp;gt; stets den Index &amp;lt;tt&amp;gt;i = r&amp;lt;/tt&amp;gt; zurück. Daher wird das Array niemals in zwei etwa gleichgroße Teile zerlegt. Die Anzahl der Vergleiche ergibt sich als&lt;br /&gt;
&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = (N+1) + C(N-1) + C(0)&amp;lt;/math&amp;gt;&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(0) = 0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
mit (N+1) Vergleichen in &amp;lt;tt&amp;gt;partition()&amp;lt;/tt&amp;gt;. Durch sukzessives Einsetzen erhalten wir:&lt;br /&gt;
&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = (N+1) + (N) + (N-1) + ... + 1 = (N+1) N / 2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In diesem Fall ist Quick Sort also nicht schneller als Selection Sort. Wir beschreiben mögliche Verbesserungen unten. Im typischen Fall (wenn nämlich das Array zufällig sortiert ist) sieht die Situation wesentlich besser aus. Bei zufälliger Sortierung wird jeder Index mit gleicher Wahrscheinlichkeit zur Pivot-Position. Wir mitteln deshalb über alle möglichen Positionen:&lt;br /&gt;
&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = (N+1) + \frac{1}{N} \sum_{k=1}^{N} \left[ C(k-1) + C(N-k) \right]&amp;lt;/math&amp;gt; für &amp;lt;math&amp;gt; N&amp;gt;0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
wobei  &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; über alle möglichen Teilungspunkte läuft. Die Summe (der mittlere Aufwand über alle möglichen Zerlegungen) kann vereinfacht werden zu&lt;br /&gt;
:::&amp;lt;math&amp;gt;\frac{1}{N} \sum_{k=1}^{N} \left[ C(k-1) + C(N-k) \right] = 2 \frac{1}{N} \sum_{k=1}^{N} C(k-1) &amp;lt;/math&amp;gt;&lt;br /&gt;
Die Auflösung der Formel ist etwas trickreich. Wir multiplizieren zunächst beide Seiten mit N:&lt;br /&gt;
&lt;br /&gt;
:::&amp;lt;math&amp;gt;&lt;br /&gt;
N \cdot C(N) = N \left[ (N+1) + \frac{2}{N} \sum_{k=1}^{N} C(k-1) \right] = N (N+1) + 2\; \sum_{k=1}^{N} C(k-1)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durch die Substitution &amp;lt;math&amp;gt;N \rightarrow N-1&amp;lt;/math&amp;gt; erhalten wir die entsprechende Formel für N-1:&lt;br /&gt;
&lt;br /&gt;
:::&amp;lt;math&amp;gt;&lt;br /&gt;
(N-1) \cdot C(N-1) = (N-1) N + 2\; \sum_{k=1}^{N-1} C(k-1)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Wir subtrahieren die Formel für N-1 von der Formel für N und eliminieren dadurch die Summe (nur der letzte Summend der ersten Summe bleibt übrig):&lt;br /&gt;
:::&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{rcl}&lt;br /&gt;
N \cdot C(N) - (N-1) \cdot C(N-1) &amp;amp;=&amp;amp; N(N+1) + 2\;\sum_{k=1}^{N} C(k-1)  - (N-1) N - 2\;\sum_{k=1}^{N-1} C(k-1)\\&lt;br /&gt;
&amp;amp;&amp;amp;\\&lt;br /&gt;
N \cdot C(N) - (N-1) \cdot C(N-1) &amp;amp;=&amp;amp; N(N+1) - (N-1) N + 2 C(N-1)&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
Durch Vereinfachen erhalten wir die rekurrente Beziehung&lt;br /&gt;
:::&amp;lt;math&amp;gt;&lt;br /&gt;
N \cdot C(N) = (N+1)\cdot C(N-1) + 2 N&amp;lt;/math&amp;gt;&lt;br /&gt;
Wir teilen jetzt beide Seiten durch &amp;lt;math&amp;gt;(N+1)N&amp;lt;/math&amp;gt;&lt;br /&gt;
:::&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{C(N)}{N+1} = \frac{C(N-1)}{N} + \frac{2}{N+1} &amp;lt;/math&amp;gt;&lt;br /&gt;
Sukzessives Einsetzen der Formel für &amp;lt;math&amp;gt; C(N-1), C(N-2) &amp;lt;/math&amp;gt; etc. bis &amp;lt;math&amp;gt;C(1)=0&amp;lt;/math&amp;gt; liefert&lt;br /&gt;
:::&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{C(N)}{N+1} = \frac{C(N-2)}{N-1} + \frac{2}{N} + \frac{2}{N+1} = \frac{C(2)}{3} + \sum_{k=3}^N\frac{2}{k+1} &amp;lt;/math&amp;gt;&lt;br /&gt;
Für hinreichend große N kann die Summe sehr genau durch ein Integral approximiert werden. Der konstanten Term kann vernachlässigt werden:&lt;br /&gt;
:::&amp;lt;math&amp;gt; &lt;br /&gt;
\frac{C(N)}{N+1} \approx 2 \sum_{k=3}^{N} \frac{1}{k+1} \approx 2 \int_1^N \frac{1}{k} dk = 2 \cdot \ln(N)&amp;lt;/math&amp;gt;&lt;br /&gt;
Somit benötigt Quick Sort im typischen Fall&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N)\approx 2 N\cdot\ln(N) \approx 1.38 N\cdot\log_2(N)&amp;lt;/math&amp;gt;&lt;br /&gt;
Vergleiche. Quick Sort ist demnach etwa genauso schnell wie Merge Sort (in der Praxis sogar etwas schneller, da die innere Schleife von Quick Sort etwas einfacher ist).&lt;br /&gt;
&lt;br /&gt;
=== Verbesserungen des Quicksort-Algorithmus ===&lt;br /&gt;
&lt;br /&gt;
==== Beseitigung der Rekursion ====&lt;br /&gt;
Eine Verbesserung beseitigt die Rekursion durch Verwendung eines Stacks. Nach jeder Partitionierung wird das größere Teilintervall auf dem Stack abgelegt und das kleinere Teilintervall direkt weiterverarbeitet (hierdurch wird sichergestellt, dass die maximale Größe des Stacks minimiert wird).&lt;br /&gt;
&lt;br /&gt;
 def quicksortNonRecursive(a, l, r):&lt;br /&gt;
     stack = [(l,r)]  # initialisiere den Stack&lt;br /&gt;
     while len(stack) &amp;gt; 0:&lt;br /&gt;
         if r &amp;gt; l:&lt;br /&gt;
             i = partition(a, l, r)&lt;br /&gt;
             if (i-l) &amp;gt; (r-i):&lt;br /&gt;
                 stack.append((l,i-1))&lt;br /&gt;
                 l = i+1&lt;br /&gt;
             else:&lt;br /&gt;
                 stack.append((i+1, r))&lt;br /&gt;
                 r = i-1&lt;br /&gt;
         else:&lt;br /&gt;
             l, r = stack.pop()&lt;br /&gt;
&lt;br /&gt;
Die ist die Methode der ''Endrekursionsbeseitigung'', die wir im Kapitel [[Iteration versus Rekursion]] ausführlich behandeln. Die folgende Skizze verdeutlicht die Verwendung des Stacks.&lt;br /&gt;
&lt;br /&gt;
 +---+---+---+---+---+---+---+&lt;br /&gt;
 | Q | U | I | C | K | S | O |&lt;br /&gt;
 +---+---+---+---+---+---+---+&lt;br /&gt;
 &lt;br /&gt;
              &lt;br /&gt;
 +---+---+---+===+---+---+---+&lt;br /&gt;
 | K | C | I |=O=| Q | S | U |&lt;br /&gt;
 +---+---+---+===+---+---+---+&lt;br /&gt;
                  \_________/&lt;br /&gt;
                      push&lt;br /&gt;
 &lt;br /&gt;
 +---+===+---+&lt;br /&gt;
 | C |=I=| K |&lt;br /&gt;
 +---+===+---+&lt;br /&gt;
          \_/&lt;br /&gt;
          push&lt;br /&gt;
 &lt;br /&gt;
 +===+&lt;br /&gt;
 |=C=|&lt;br /&gt;
 +===+&lt;br /&gt;
 &lt;br /&gt;
         +===+&lt;br /&gt;
         |=K=|&lt;br /&gt;
         +===+&lt;br /&gt;
 &lt;br /&gt;
                 +---+---+===+&lt;br /&gt;
                 | Q | S |=U=|&lt;br /&gt;
                 +---+---+===+&lt;br /&gt;
 &lt;br /&gt;
                 +---+===+&lt;br /&gt;
                 | Q |=S=|&lt;br /&gt;
                 +---+===+&lt;br /&gt;
 &lt;br /&gt;
                 +===+&lt;br /&gt;
                 |=Q=|&lt;br /&gt;
                 +===+&lt;br /&gt;
         &lt;br /&gt;
 +---+---+---+---+---+---+---+&lt;br /&gt;
 | C | I | K | O | Q | S | U |  &lt;br /&gt;
 +---+---+---+---+---+---+---+&lt;br /&gt;
&lt;br /&gt;
==== Alternatives Sortieren kleiner Intervalle ====&lt;br /&gt;
&lt;br /&gt;
Für kleine Arrays (bis zu einer gegebenen Größe K) ist das &amp;quot;Teile und herrsche&amp;quot;-Prinzip nicht die effizienteste Herangehensweise. Insbesondere kann man ein Array mit maximal 3 Elementen direkt sortieren:&lt;br /&gt;
 def sortThree(a, l, r):&lt;br /&gt;
     if r &amp;gt; l and a[l+1] &amp;lt; a[l]:          # Stelle sicher, dass a[l] und a[l+1] relativ zueinander sortiert sind.&lt;br /&gt;
         a[l], a[l+1] = a[l+1], a[l]&lt;br /&gt;
     if r == l + 2:&lt;br /&gt;
         if a[r] &amp;lt; a[l]:                  # Stelle sicher, dass a[l] und a[r] relativ zueinander sortiert sind.&lt;br /&gt;
             a[l], a[r] = a[r], a[l]      # Danach ist a[l] auf jeden Fall das kleinste Element.&lt;br /&gt;
         if a[r] &amp;lt; a[r-1]:                # Stelle sicher, dass a[r-1] und a[r] relativ zueinander sortiert sind.&lt;br /&gt;
             a[r], a[r-1] = a[r-1], a[r]  # Jetzt ist a[r] auf jeden Fall das größte Element und das Array damit sortiert.&lt;br /&gt;
&lt;br /&gt;
In die Funktion &amp;lt;tt&amp;gt;quicksort()&amp;lt;/tt&amp;gt; wird jetzt ein Aufruf dieser Funktion eingefügt:&lt;br /&gt;
     if r &amp;gt; l + 2:&lt;br /&gt;
         # wie bisher&lt;br /&gt;
     elif r &amp;gt; l:&lt;br /&gt;
         sortThree(a, l, r)&lt;br /&gt;
&lt;br /&gt;
==== Günstige Selektion des Pivot-Elements ====&lt;br /&gt;
Durch geschickte Wahl des Pivot-Elements kann man erreichen, dass der ungünstigste Fall (quadratische Laufzeit) nur mit sehr kleiner Wahrscheinlichkeit eintritt. Zwei Möglichkeiten haben sich bewährt:&lt;br /&gt;
# Anstatt des letzten Elements des Teilarrays wählt man ein zufälliges Element (mit Hilfe eines Zufallszahlengenerators). Dadurch wird Quick Sort unempfindlich gegenüber bereits sortierten Arrays, weil die Teilung im Mittel wie bei einem zufällig sortierten Array erfolgt (typischer Fall in obiger Laufzeitberechnung).&lt;br /&gt;
# Median (mittlerer Wert) von drei Elementen: Verwende den Median des ersten, mittleren und letzten Elements jedes Teilarrays als Pivot-Element.&lt;br /&gt;
In beiden Fällen ist es praktisch ausgeschlossen, dass ein Eingabearray so angeordnet ist, dass in jedem Teilarray gerade das kleinste oder größte Element als Pivot gewählt wird. Nur dann könnte der ungünstigste Fall jedoch eintreten, was somit effektiv verhindert wird.&lt;br /&gt;
&lt;br /&gt;
[[Korrektheit|Nächstes Thema]]&lt;/div&gt;</summary>
		<author><name>Jschleic</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Randomisierte_Algorithmen&amp;diff=4739</id>
		<title>Randomisierte Algorithmen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Randomisierte_Algorithmen&amp;diff=4739"/>
				<updated>2010-08-14T07:22:33Z</updated>
		
		<summary type="html">&lt;p&gt;Jschleic: /* 1. Randomisierte Algorithmen */ Formet&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 1. Randomisierte Algorithmen ==&lt;br /&gt;
&lt;br /&gt;
'''Def.:''' Algorithmen, die bei Entscheidung oder bei der Wahl der Parameter Zufallszahlen benutzen&lt;br /&gt;
&lt;br /&gt;
'''Bsp.:''' Lösen des K-SAT-Problems durch RA&lt;br /&gt;
    geg.: logischer Ausdruck in K-CNF (n Variablen, m Klauseln, k Variablen pro Klausel)&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;math&amp;gt;\underbrace {\underbrace {\left(x_1 \vee x_3 \vee...\right)}_{k\; Variablen} \wedge \left( x_2 \vee x_4 \vee...\right)}_{m\;Klauseln}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    for i in range (trials):    #Anzahl der Versuche&lt;br /&gt;
         #Bestimme eine Zufallsbelegung des &amp;lt;math&amp;gt;\{ x_i \}&amp;lt;/math&amp;gt;:&lt;br /&gt;
         for j in range (steps):&lt;br /&gt;
               if &amp;lt;math&amp;gt;\{ x_i \}&amp;lt;/math&amp;gt; erfüllt alle Klauseln: return &amp;lt;math&amp;gt;\{ x_i \}&amp;lt;/math&amp;gt;&lt;br /&gt;
               #wähle zufällig eine Klausel, die nicht erfüllt ist und negiere zufällig eine der Variablen in dieser Klausel &lt;br /&gt;
               (die Klausel ist jetzt erfüllt)&lt;br /&gt;
    return None&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Eigenschaft: falls &amp;lt;math&amp;gt;k&amp;gt;2&amp;lt;/math&amp;gt; : steps *trials &amp;lt;math&amp;gt;\in O\left(\Alpha^n \right) \Alpha &amp;gt;1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
z.B. &amp;lt;math&amp;gt;k=3&amp;lt;/math&amp;gt; steps=3*n, trials=&amp;lt;math&amp;gt;\left(\frac{4}3\right)^n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
aber: bei &amp;lt;math&amp;gt;k=2&amp;lt;/math&amp;gt; sind im Mittel nur steps=&amp;lt;math&amp;gt;O\left(n^2\right)&amp;lt;/math&amp;gt; nötig, trials=&amp;lt;math&amp;gt;O\left(1\right)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''-Zufallsbelegung hat  &amp;lt;math&amp;gt;t\leq n&amp;lt;/math&amp;gt; richtige Variablen (im Mittel &amp;lt;math&amp;gt;t\approx \frac {n} 2&amp;lt;/math&amp;gt;)'''&lt;br /&gt;
&lt;br /&gt;
Negieren einer Variable ändert t um 1,&lt;br /&gt;
u.Z. &amp;lt;math&amp;gt;t\rightarrow t+1&amp;lt;/math&amp;gt; mit Wahrscheinlichkeit &amp;lt;math&amp;gt;\frac 1 2&amp;lt;/math&amp;gt; ::(für beliebiges k: &amp;lt;math&amp;gt;\frac 1 k&amp;lt;/math&amp;gt;)&lt;br /&gt;
::::::::::&amp;lt;math&amp;gt;t\rightarrow t-1&amp;lt;/math&amp;gt; mit Wahrscheinlichkeit &amp;lt;math&amp;gt;\frac 1 2&amp;lt;/math&amp;gt; ::(für beliebiges k: &amp;lt;math&amp;gt;\frac {k-1} k&amp;lt;/math&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''-Wieviele Schritte braucht man im Mittel, um zu einer Lösung mit t Richtigen zu kommen?'''&lt;br /&gt;
&lt;br /&gt;
       &amp;lt;math&amp;gt;S\left(t\right)=\frac 1 2 S\left(t-1\right) + \frac 1 2 S\left(t+1\right) +1&amp;lt;/math&amp;gt;&lt;br /&gt;
       &lt;br /&gt;
       &amp;lt;math&amp;gt;S\left(n\right)=0&amp;lt;/math&amp;gt;    #Abbruchbedingung der Schleife&lt;br /&gt;
       &lt;br /&gt;
       &amp;lt;math&amp;gt;S\left(0\right) = S\left( 1\right) + 1 \Rightarrow S\left(t\right) = n^2-t^2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
       '''Probe:''' &lt;br /&gt;
 &lt;br /&gt;
       &amp;lt;math&amp;gt;&lt;br /&gt;
       \begin{align} &lt;br /&gt;
             S\left(n\right) &amp;amp; = n^2-n^2=0 \\&lt;br /&gt;
                  &lt;br /&gt;
             S\left(0\right) &amp;amp;= n^2-0^2 \\&lt;br /&gt;
              &lt;br /&gt;
                   &amp;amp;= S\left(1\right)+1 \\&lt;br /&gt;
              &lt;br /&gt;
                   &amp;amp;= n^2-1^2+1 \\&lt;br /&gt;
              &lt;br /&gt;
                   &amp;amp;= n^2 \\&lt;br /&gt;
                 &lt;br /&gt;
              S\left(t\right) &amp;amp;= \frac 1 2 \left(n^2-\left(t-1\right)^2\right) + \frac 1 2 \left(n^2-\left(t+1\right)^2\right)+1 \\&lt;br /&gt;
              &lt;br /&gt;
                   &amp;amp;= \frac 1 2 n^2-\frac 1 2 \left( t^2-2t+1\right) + \frac 1 2 n^2-\frac 1 2 \left(t^2+2t+1\right) + 1 \\&lt;br /&gt;
              &lt;br /&gt;
                   &amp;amp;= n^2-t^2&lt;br /&gt;
       \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Das ist das Random Walk Problem'''&lt;br /&gt;
&lt;br /&gt;
Im ungünstigsten Fall (t=0) werden im Mittel &amp;lt;math&amp;gt;n^2&amp;lt;/math&amp;gt; Schritte benötigt, um durch random walk nach t=n zu gelangen.&lt;br /&gt;
&lt;br /&gt;
== 2. RANSAC-ALGORITHMUS (Random Sample Consensus)==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;u&amp;gt;''Aufgabe:''&amp;lt;/u&amp;gt; gegeben: Datenpunkte&lt;br /&gt;
::gesucht: Modell, das die Datenpunkte erklärt&lt;br /&gt;
&lt;br /&gt;
[[Image:Rubto.png|thumb|250px|none]]&lt;br /&gt;
&lt;br /&gt;
'''Messpunkte:'''&lt;br /&gt;
   &lt;br /&gt;
      übliche Lösung: Methode der kleinsten Quadrate&lt;br /&gt;
      &lt;br /&gt;
      &amp;lt;math&amp;gt;\min_{a,b} 	\sum_{i} \left(a x_i + b + y_i\right)^2&amp;lt;/math&amp;gt;&lt;br /&gt;
      &lt;br /&gt;
      Schulmathematik:      &amp;lt;math&amp;gt;Minimum\stackrel{\wedge}{=}Ableitung=0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Lineares Gleichungssystem'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\frac{d}{da}\sum{i} \left(ax_i+b-y_i\right)^2=\sum{i} \frac{d}{da} \left[ax_i+b-y_i\right)^2&amp;lt;/math&amp;gt;  &lt;br /&gt;
&lt;br /&gt;
::::&amp;lt;math&amp;gt;f\left(g\left(x\right)\right)&amp;lt;/math&amp;gt;   &lt;br /&gt;
&lt;br /&gt;
::::&amp;lt;math&amp;gt;f\left(x\right)=x^2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
::::&amp;lt;math&amp;gt;y\left(a\right)=ax_i+b-y_i&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;=\sum_{i}2\left(ax_i+b-y_i\right)\frac{d}{da} \underbrace {ax_i+b-y_i}_{x_i}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\underline {=2\sum_{i}\left(ax_i+b-y_i\right)x_i\stackrel{!}{=}0}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
::::::&amp;lt;math&amp;gt;a\sum_{i}{x_i}^2+b\sum_{i}x_i=\sum_{i}x_iy_i&amp;lt;/math&amp;gt;   &lt;br /&gt;
&lt;br /&gt;
::::::&amp;lt;math&amp;gt;a\sum_{i}x_i+b\sum_{i}1=\sum_{i}y_i&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\frac{d}{db}\sum_{i}\left(ax_i+b-y_i\right)^2=2\sum_{i}\left(ax_i+b-y_i\right)*1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
:Problem: &amp;lt;math&amp;gt;\epsilon  %&amp;lt;/math&amp;gt; der Datenpunkte sind Outlier&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\Longrightarrow&amp;lt;/math&amp;gt; Einfaches Anpassen des Modells an die Datenpunkte funktioniert nicht&lt;br /&gt;
&lt;br /&gt;
:Seien mindestens k Datenpunkte notwendig, um das Programm anpassen zu können&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
RANSAC-Algorithmus&lt;br /&gt;
&lt;br /&gt;
      for  l in range (trials):&lt;br /&gt;
           wähle zufällig k Punkte aus&lt;br /&gt;
           passe das Modell an die k Punkte an&lt;br /&gt;
           zähle, wieviele Punkte in der Nähe des Modells liegen (d.h. &amp;lt;math&amp;gt;d_i &amp;lt; d_max&amp;lt;/math&amp;gt; muss geschickt gewählt werden) &lt;br /&gt;
                                           #Bsp. Geradenfinden:-wähle a,b aus zwei Punkten&lt;br /&gt;
                                                               -berechne: &amp;lt;math&amp;gt;|ax_i+b-y_i|=d_i&amp;lt;/math&amp;gt;&lt;br /&gt;
                                                               -zähle Punkt i als Inlier, falls &amp;lt;math&amp;gt;d_i&amp;lt;d_ma&amp;lt;/math&amp;gt;&lt;br /&gt;
      return: Modell mit höchster Zahl der Inlier&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      &amp;lt;math&amp;gt;trials= \frac{log\left(1-p\right)}{log\left(1-\left(1-\epsilon\right)^k\right)}&amp;lt;/math&amp;gt;  mit k=Anzahl der Datenpunkte und p=Erfolgswahrscheinlichkeit, &amp;lt;math&amp;gt;\epsilon&amp;lt;/math&amp;gt;=Outlier-Anteil&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Erfolgswahrscheinlichkeit: p=99%'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\begin{array}{|c||c|c|c|c|c|}&lt;br /&gt;
         Beispiel &amp;amp; k &amp;amp; \epsilon=10% &amp;amp; 20% &amp;amp; 50% &amp;amp; 70%\\&lt;br /&gt;
         \hline&lt;br /&gt;
         Linie\;in\;2D &amp;amp; 2 &amp;amp; 3 &amp;amp;5 &amp;amp; 17 &amp;amp; 49\\&lt;br /&gt;
         Kreis\;in\;2D &amp;amp; 3 &amp;amp; 4 &amp;amp; 7 &amp;amp; 35 &amp;amp; 169\\&lt;br /&gt;
         Ebene\;in\;3D &amp;amp; 8 &amp;amp; 9 &amp;amp; 26 &amp;amp; 1172 &amp;amp; 70188\\&lt;br /&gt;
       \end{array}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Ein Spiel: Wie viel Schritte braucht man im Mittel zum Ziel?'''&lt;br /&gt;
&lt;br /&gt;
   geg.: 5 Plätze, 2 Personen: eine Person rückt vom einem Platz zu dem enderen Platz;&lt;br /&gt;
         die zweite Person wirft die Münze.&lt;br /&gt;
         Wenn die Münze auf Kopf landet, rücke nach rechts und wenn die Münze auf Zahl landet, rücke nach links.&lt;br /&gt;
         &amp;lt;--- Zahl                                                         Kopf--&amp;gt;&lt;br /&gt;
         Kopf: /////&lt;br /&gt;
         Zahl: /// &lt;br /&gt;
&lt;br /&gt;
:: =&amp;gt; mit 8 Schritten bis zum Ziel&lt;br /&gt;
:im Mittel: bei N Plätzen braucht man N&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; Schritte&lt;br /&gt;
&lt;br /&gt;
: all: mit N&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; Schritten um N Plätze rücken&lt;br /&gt;
: Wie viel Schritte braucht man im Mittel zum Ziel?&lt;br /&gt;
&lt;br /&gt;
         &amp;lt;math&amp;gt;S\left(N\right)=0&amp;lt;/math&amp;gt;    #wenn wir uns im Stuhl Nr.1 befinden&lt;br /&gt;
           &lt;br /&gt;
         &amp;lt;math&amp;gt;S\left(i\right)=\frac 1 2 S\left(1 + S\left(i+1\right)\right) + \frac 1 2 S\left(1 + S\left(i-1\right)\right) = \frac 1 2 S\left(i+1\right) + \frac 1 2 S\left(i-1\right) +1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
         &amp;lt;math&amp;gt;S\left(0\right)=1 + S\left(1\right)&amp;lt;/math&amp;gt;    #bei 0.Platz&lt;br /&gt;
&lt;br /&gt;
:::*Lösung: &lt;br /&gt;
&lt;br /&gt;
         &amp;lt;math&amp;gt;S\left(i\right)= N^2 - i^2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:::*speziell: &lt;br /&gt;
&lt;br /&gt;
         &amp;lt;math&amp;gt;S\left(i\right)= N^2&amp;lt;/math&amp;gt;           #wenn man am ungünstigsten Platz startet&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
'''Beziehung zu randomisiertem 2-SAT'''&lt;br /&gt;
&lt;br /&gt;
      &amp;quot;Platz &amp;lt;math&amp;gt;i&amp;lt;/math&amp;gt; &amp;quot;: &amp;lt;math&amp;gt;i&amp;lt;/math&amp;gt; Variablen haben den richtigen Wert,  &amp;lt;math&amp;gt;\left(N-i\right)&amp;lt;/math&amp;gt;  sind falsch gesetzt&lt;br /&gt;
&lt;br /&gt;
      &amp;lt;math&amp;gt;S\left(\frac N 2\right)=N^2 - \left(\frac N 2\right)^2 = N^2 - \frac N 4 ^2 = \frac 3 4 N^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
      &amp;lt;math&amp;gt;S\left(\frac N 2\right)&amp;lt;/math&amp;gt;     # Anfangszustand&lt;br /&gt;
----&lt;br /&gt;
== '''Las Vegas vs. Monte Carlo'''==&lt;br /&gt;
&lt;br /&gt;
   * ''Las Vegas - Algorithmen''&lt;br /&gt;
     - Ergebnis ist immer korrekt.&lt;br /&gt;
     - Berechnung ist mit hoher Wahrscheinlichkeit effizient (d.h. Randomisierung macht den ungünstigsten Fall unwahrscheinlich).&lt;br /&gt;
&lt;br /&gt;
   * ''Monte Carlo - Algorithmen''&lt;br /&gt;
     - Berechnung immer effizient.&lt;br /&gt;
     - Ergebnis mit hoher Wahrscheinlichkeit korrekt (falls kein effizienter Algorithmus bekannt, der immer die richtige Lösung liefert).&lt;br /&gt;
&lt;br /&gt;
{| border = &amp;quot;1&amp;quot; cellspacing=&amp;quot;0&amp;quot; cellpadding=&amp;quot;5&amp;quot; align=&amp;quot;center&amp;quot;&lt;br /&gt;
|- &lt;br /&gt;
! Las Vegas&lt;br /&gt;
! Monte Carlo&lt;br /&gt;
|- &lt;br /&gt;
| - Erzeugen einer perfekten Hashfuktion &lt;br /&gt;
| - Algorithmus von Freiwald(Matrizenmultiplikation)&lt;br /&gt;
|-&lt;br /&gt;
| - universelles Hashing&lt;br /&gt;
| - RANSAC&lt;br /&gt;
|-&lt;br /&gt;
| - Quick Sort mit zufälliger Wahl des Pivot-Elements&lt;br /&gt;
| - randomisierte K-SAT(k&amp;gt;=3)(Alg. von Schöning)&lt;br /&gt;
|-&lt;br /&gt;
| - Treep mit zufälligen Prioritäten&lt;br /&gt;
| -&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== ''' Zufallszahlen ''' ==&lt;br /&gt;
&lt;br /&gt;
:- kann man nicht mit deterministischen Computern erzeugen&lt;br /&gt;
:- aber man kann Pseudo-Zufallszahlen erzeugen, die viele Eigenschaften von echten Zufallszahlen haben&lt;br /&gt;
::: * sehr ähnlich  zum Hash&lt;br /&gt;
&lt;br /&gt;
     ''&amp;quot;linear Conguential Random number generator&amp;quot;''&lt;br /&gt;
        &amp;lt;math&amp;gt;I_{i+1}= \left(a*I_i + c\right)mod m&amp;lt;/math&amp;gt;&lt;br /&gt;
        &amp;lt;math&amp;gt;\begin{array}{ll}&lt;br /&gt;
        \mathrm{=&amp;gt; } &amp;amp; I_i \in [0, m-1]\\&lt;br /&gt;
&lt;br /&gt;
        \end{array}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
:-sorgfältige Wahl von  a, c, m notwendig&lt;br /&gt;
::'''Bsp.'''  m = 2&amp;lt;sup&amp;gt;32&amp;lt;/sup&amp;gt;&lt;br /&gt;
::: a = 1664525, c = 1013904223&lt;br /&gt;
::: ''&amp;quot;quick and dirty generator&amp;quot;''&lt;br /&gt;
&lt;br /&gt;
==='''Nachteile'''===&lt;br /&gt;
&lt;br /&gt;
* nicht zufällig genug für viele Anwendungen&lt;br /&gt;
::'''Bsp.''' wähle Punkt in R&amp;lt;sup&amp;gt;3&amp;lt;/sup&amp;gt;&lt;br /&gt;
&lt;br /&gt;
::&amp;lt;math&amp;gt;\begin{array}{ll}&lt;br /&gt;
      \mathrm{ } &amp;amp; p = (rand(), rand(), rand())\\&lt;br /&gt;
&lt;br /&gt;
      \end{array}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
::gibt Zahl u, v, w so, dass &lt;br /&gt;
&lt;br /&gt;
::&amp;lt;math&amp;gt;\begin{array}{ll}&lt;br /&gt;
        \mathrm{ } &amp;amp; u * p[0] + v * p[1] + w * p[3]\\&lt;br /&gt;
&lt;br /&gt;
        \end{array}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
::stark geclustert ist.&lt;br /&gt;
&lt;br /&gt;
* Periodenlänge ist zu kurz:&lt;br /&gt;
:: spätestens nach m Schritten wiederholt sich die Folge&lt;br /&gt;
&lt;br /&gt;
::'''allgemein''': falls der interne Zustand des Zufallsgenerators ''k'' bits hat, ist Periodenlänge:&lt;br /&gt;
&lt;br /&gt;
::&amp;lt;math&amp;gt;\begin{array}{ll}&lt;br /&gt;
        \mathrm{ } &amp;amp; Periode &amp;lt; 2^k\\&lt;br /&gt;
&lt;br /&gt;
        \end{array}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* ''lowbits'' sind weniger zufällig als die ''highbits''&lt;br /&gt;
----&lt;br /&gt;
=== ''Mersenne Twister''===&lt;br /&gt;
   &lt;br /&gt;
&lt;br /&gt;
'''bester zur Zeit bekannter Zufallszahlengenerator (ZZG)'''&lt;br /&gt;
* innere Zustand: &amp;lt;math&amp;gt;\begin{array}{ll}&lt;br /&gt;
        \mathrm{ } &amp;amp; 624*32 bit\ Integers  =&amp;gt; 19968 bits\\&lt;br /&gt;
        \end{array}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Periodenlänge: &amp;lt;math&amp;gt;2^ {19937} \approx 4 * 10^{6000}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Punkte aus aufeinanderfolgende Zufallszahlen in &amp;lt;math&amp;gt;\mathbb{R}^n&amp;lt;/math&amp;gt; sind gleich verteilt bis &amp;lt;math&amp;gt;\begin{array}{ll}&lt;br /&gt;
        \mathrm{ } &amp;amp; n = 623\\&lt;br /&gt;
        \end{array}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* alle Bits sind unabhängig voneinander zufällig (&amp;quot;Twister&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
* schnell&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  class Random:&lt;br /&gt;
    def __init__(self, seed):&lt;br /&gt;
        self.N = 624&lt;br /&gt;
        self.state = [0]*624&lt;br /&gt;
        self.state = zufällig mit Hilfe des ''seeds'' initialisieren (einfacher ZZG)&lt;br /&gt;
        self.i = 0    # zählt mit in welchem Zustand wir gerade aufhalten&lt;br /&gt;
 &lt;br /&gt;
    def __call__(self):&lt;br /&gt;
        N,M = 624, 397&lt;br /&gt;
        i = self.i&lt;br /&gt;
        r = (self.state[i] &amp;amp; 0x80000000)|(self.state[(i+1)%N] &amp;amp; 0x7FFFFFFF)     # aktualisieren&lt;br /&gt;
        if self.state[(i+1)%N]&amp;amp;1:                                               # des Zustands&lt;br /&gt;
           r^= 0x9908B0DF&lt;br /&gt;
        self.state[i] = self.state[(i+1)%N]*^r&lt;br /&gt;
 &lt;br /&gt;
        y = self.state[i]&lt;br /&gt;
           self.i = (self.i + 1)%N&lt;br /&gt;
           # bits verwürfeln&lt;br /&gt;
           y ^= (y&amp;gt;&amp;gt;11)&lt;br /&gt;
           y ^= ((y&amp;gt;&amp;gt;7) &amp;amp; 0x9D2C5680)&lt;br /&gt;
           y ^= ((y&amp;gt;&amp;gt;15) &amp;amp; 0xEFC60000)&lt;br /&gt;
           y ^= (y&amp;gt;&amp;gt;18)&lt;br /&gt;
         return y&lt;br /&gt;
&lt;br /&gt;
'''geg.:''' Zufallszahl &lt;br /&gt;
&amp;lt;math&amp;gt;\begin{array}{ll}&lt;br /&gt;
        \mathrm{ } &amp;amp; [0, \overbrace{2^{32}-1}^{m-1}]\\&lt;br /&gt;
        \end{array}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''ges.:''' Zufallszahl&lt;br /&gt;
&amp;lt;math&amp;gt;\begin{array}{ll}&lt;br /&gt;
        \mathrm{ } &amp;amp; [0, k - 1]\\&lt;br /&gt;
        \end{array}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''naive Lösung:'''  &amp;lt;math&amp;gt;\begin{array}{ll}&lt;br /&gt;
        \mathrm{ } &amp;amp; rand()%k\\&lt;br /&gt;
        \end{array}&amp;lt;/math&amp;gt;  ist schlecht.&lt;br /&gt;
&lt;br /&gt;
'''Bsp.'''&lt;br /&gt;
&amp;lt;math&amp;gt;\begin{array}{ll}&lt;br /&gt;
        \mathrm{ } &amp;amp; \qquad m = 16\qquad k = 11\\&lt;br /&gt;
        \end{array}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;0&amp;quot; cellpadding=&amp;quot;5&amp;quot; &lt;br /&gt;
! rand() || 0 || 1 || 2 || 3 || 4 || 5 || 6 || 7 || 8 || 9 || 10 || 11 || 12 || 13 || 14 || 15&lt;br /&gt;
|-&lt;br /&gt;
! rand()%k&lt;br /&gt;
! 0 || 1 || 2 || 3 || 4 || 5 || 6 || 7 || 8 || 9 || 10 || 0 || 1 || 2 || 3 || 4 &lt;br /&gt;
|-&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=&amp;gt; 0,...,n kommt doppelt so häufig wie 5,...,10 &amp;quot;nicht zufällig&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Lösung:'''  Zurückweisen des Rests der Zahlen (''rejektion sampling'')&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\begin{array}{ll}&lt;br /&gt;
        \mathrm{ } &amp;amp; remainder = (m - 1 - (k - 1))% k = (m - k)%k\\&lt;br /&gt;
        \mathrm{ } &amp;amp; last\ Good\ Value = m-1-remainder\\&lt;br /&gt;
        \end{array}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
  r = rand()&lt;br /&gt;
  while r &amp;gt; last.GoodValue:&lt;br /&gt;
        r = rand()&lt;br /&gt;
        return r%k&lt;br /&gt;
&lt;br /&gt;
[[Greedy-Algorithmen und Dynamische Programmierung|Nächstes Thema]]&lt;/div&gt;</summary>
		<author><name>Jschleic</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Graphen_und_Graphenalgorithmen&amp;diff=4736</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=4736"/>
				<updated>2010-08-12T16:29:39Z</updated>
		
		<summary type="html">&lt;p&gt;Jschleic: /* Stirling'sche Formel */ typo&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 Abbildung) einen Spaziergang zu unternehmen, bei dem jede der 7 Brücken genau einmal überquert wird?&lt;br /&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) \wedge (\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>Jschleic</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Graphen_und_Graphenalgorithmen&amp;diff=4735</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=4735"/>
				<updated>2010-08-12T09:06:14Z</updated>
		
		<summary type="html">&lt;p&gt;Jschleic: bäume zu Definitionen, einige Typos und Codekorrekturen&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 Abbildung) einen Spaziergang zu unternehmen, bei dem jede der 7 Brücken genau einmal überquert wird?&lt;br /&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 Sterling 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) \wedge (\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>Jschleic</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Graphen_und_Graphenalgorithmen&amp;diff=4734</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=4734"/>
				<updated>2010-08-12T07:51:05Z</updated>
		
		<summary type="html">&lt;p&gt;Jschleic: /* Definitionen */ typo&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 Abbildung) einen Spaziergang zu unternehmen, bei dem jede der 7 Brücken genau einmal überquert wird?&lt;br /&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;
=== 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;
== Bäume und Wälder ==&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;
== 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;
        '''&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;
        '''&lt;br /&gt;
        def visit(graph, node, visited):&lt;br /&gt;
                '''&lt;br /&gt;
                visited ist Array mit Flags fuer besuchte Knoten&lt;br /&gt;
                '''&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;
&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 moeglicher 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;
* fuer gerichtete Graphen gilt zusaetzlich: 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;
                '''&lt;br /&gt;
                anchor ist Anker der aktuellen ZK&lt;br /&gt;
                '''&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;
                '''&lt;br /&gt;
                #Prueft auf anchors[k]==k&lt;br /&gt;
                '''&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;
&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 Sterling 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) \wedge (\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>Jschleic</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Hashing_und_assoziative_Arrays&amp;diff=4732</id>
		<title>Hashing und assoziative Arrays</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Hashing_und_assoziative_Arrays&amp;diff=4732"/>
				<updated>2010-08-10T15:06:44Z</updated>
		
		<summary type="html">&lt;p&gt;Jschleic: /* Implementation */ typo&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Die Mitschrift gibts auch als [http://hci.iwr.uni-heidelberg.de/alda/images/AlDa.pdf PDF].&lt;br /&gt;
&lt;br /&gt;
== Assoziative Arrays ==&lt;br /&gt;
Assoziative Arrays werden genau wie gewöhnliche  Arrays benutzt, sie unterstützen also den lesenden und schreibenden Zugriff über einen Index i&lt;br /&gt;
    X = a[i]&lt;br /&gt;
    a[i] = x&lt;br /&gt;
Im Unterschied zum gewöhnlichen Array, wo i ein Integer im Bereich &amp;lt;math&amp;gt; i \in 0 \ldots N-1&amp;lt;/math&amp;gt; sein muss, kann der Typ von i jetzt ''beliebig'' sein. Eine typische Anwendung ist ein Wörterbuch&lt;br /&gt;
    x = toEnglish['Baum']   # ergibt 'tree'&lt;br /&gt;
In diesem Fall ist der Typ des Index &amp;lt;tt&amp;gt;string&amp;lt;/tt&amp;gt;, was in der Praxis der häufigste Fall ist, weshalb assoziative Arrays oft als ''Dictionary'' bezeichnet werden (so auch in Python, hier heißt der Typ &amp;lt;tt&amp;gt;dict&amp;lt;/tt&amp;gt;). Im allgemeinen kann aber jeder Typ als Index benutzt werden, für den entweder eine Ordnung oder eine Hashfunktion definiert ist. In erstem Fall realisiert man das assoziative Array mit Hilfe eines Suchbaums, in zweiten Fall mit Hilfe einer Hashtabelle.&lt;br /&gt;
&lt;br /&gt;
==Realisierung des assoziativen Arrays als Suchbaum== &lt;br /&gt;
&lt;br /&gt;
Wenn für den Indextyp des assoziativen Arrays eine Ordnung definiert ist (wenn also &amp;lt;tt&amp;gt;i1 &amp;lt; i2&amp;lt;/tt&amp;gt; oder &amp;lt;tt&amp;gt;cmp(i1, i2)&amp;lt;/tt&amp;gt; unterstützt werden), kann man das Indexierungsproblem auf das Suchproblem zurückführen, indem man die Indizes als Suchschlüssel verwendet. Im einfachsten Fall kann dies mit sequentieller Suche realisiert werden: Man verwendet ein gewöhnliches Array, dessen Einträge (Schlüssel, Daten)-Paare sind. Bei der Frage nach einem Schlüssel wird das betreffende Paar gesucht und die darin gespeicherten Daten zurückgegeben. Dies erfordert aber einen Suchaufwand in O(n). Effizienter geht es mit einem Suchbaum. Die Datenstruktur des Suchbaums muss dafür so erweitert werden, dass zu jedem Schlüssel (=Index) weitere Informationen gespeichert werden können (nämlich der Inhalt des indexierten Feldes). Man erweitert die Node-Klasse um ein Feld &amp;quot;data&amp;quot;:&lt;br /&gt;
    class Node:&lt;br /&gt;
        def __init__(self, key, data = None):&lt;br /&gt;
            self.key = key&lt;br /&gt;
            self.data = data&lt;br /&gt;
            self.left = self.right = None&lt;br /&gt;
Dann kann man eine Klasse &amp;lt;tt&amp;gt;AssociativeArray&amp;lt;/tt&amp;gt; realisieren, die die Indexoperationen intern mit Hilfe der Baumsuche implementiert. In Python wird der Zugriffsoperator ''[ ]'' für eine Datenstruktur (also innerhalb einer Klasse) wie folgt implementiert (vgl. die [http://docs.python.org/ref/sequence-types.html Python Docs zum Thema]):&lt;br /&gt;
        def __setitem__(self, key, value)&lt;br /&gt;
so dass im Programmtext dann folgende Syntax möglich ist: &amp;lt;tt&amp;gt;a[key] = value&amp;lt;/tt&amp;gt; (schreibender Zugriff auf &amp;lt;tt&amp;gt;a[key]&amp;lt;/tt&amp;gt;).&lt;br /&gt;
Analog wird der lesende Zugriff &amp;lt;tt&amp;gt;value = a[key]&amp;lt;/tt&amp;gt; wie folgt umgesetzt:&lt;br /&gt;
        def __getitem__(self, key, value)&lt;br /&gt;
Der Konstruktor der Klasse &amp;lt;tt&amp;gt;AssociativeArray&amp;lt;/tt&amp;gt; initialisiert einen leeren Suchbaum:&lt;br /&gt;
    class AssociativeArray:&lt;br /&gt;
        def __init__(self):&lt;br /&gt;
            self.root = None&lt;br /&gt;
            self.size = 0&lt;br /&gt;
Die Funktion &amp;lt;tt&amp;gt;__setitem__&amp;lt;/tt&amp;gt; schaut nach, ob ein Eintrag mit dem betreffenden Index bereits existiert. Wenn ja, werden seine Daten mit den neuen Daten überschrieben, andernfalls wird ein neuer Eintrag angelegt. Intern werden dazu die bereits bekannten Funktionen &amp;lt;tt&amp;gt;treeSearch&amp;lt;/tt&amp;gt; und &amp;lt;tt&amp;gt;treeInsert&amp;lt;/tt&amp;gt; verwendet (siehe Abschnitt [[Suchen#Suchbäume|Suchbäume]]):&lt;br /&gt;
        def __setitem__(self, index, data):&lt;br /&gt;
             node = treeSearch(self.root, index)&lt;br /&gt;
             if node is None:&lt;br /&gt;
                 self.root = treeInsert(self.root, index)&lt;br /&gt;
                 self.size += 1&lt;br /&gt;
                 node = treeSearch(self.root, index) &lt;br /&gt;
             node.data = data&lt;br /&gt;
(Eine geschicktere Implementation würde natürlich die wiederholten Aufrufe von &amp;lt;tt&amp;gt;treeSearch&amp;lt;/tt&amp;gt; und &amp;lt;tt&amp;gt;treeInsert&amp;lt;/tt&amp;gt; eliminieren. Dies ändert aber nichts an der Komplexität der Funktion.) Die Funktion &amp;lt;tt&amp;gt;__getitem__&amp;lt;/tt&amp;gt; sucht ebenfalls einen Eintrag mit dem gegebenen Index. Wenn er gefunden wird, gibt sie die zugehörigen Daten zurück, andernfalls eine Fehlermeldung:&lt;br /&gt;
        def __getitem__(self, index):&lt;br /&gt;
            node = treeSearch(self.root, index)&lt;br /&gt;
            if node is None:&lt;br /&gt;
                raise KeyError(index)&lt;br /&gt;
            else:&lt;br /&gt;
                return node.data&lt;br /&gt;
Die Indexoperationen haben bei der Realisierung mit Baumsuche eine Komplexität in O(log n).&lt;br /&gt;
&lt;br /&gt;
Ein wichtiges Beispiel für ein assoziatives Array, das auf diese Weise realisiert wurde, ist die C++ Standardklasse &amp;lt;tt&amp;gt;[http://www.sgi.com/tech/stl/Map.html std::map]&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Hashing ==&lt;br /&gt;
&lt;br /&gt;
Nun stellt sich die Frage, ob die Suche in einem assoziativen Array auch schneller, möglicherweise sogar in O(1), geht. Die Antwort lautet: Ja, wenn für die Indexklasse eine Hashfunktion definiert ist.&lt;br /&gt;
&lt;br /&gt;
===Hashfunktionen===&lt;br /&gt;
&lt;br /&gt;
Gegeben sei ein Universum U, dass die Menge aller legalen Schlüssel darstellt. Die Mächtigkeit |U| der Menge U ist im allgemeinen sehr groß. Beispielsweise kann man mit Strings der Länge 9 bis zu 27&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt;&amp;amp;asymp;10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt;&amp;amp;asymp;2&amp;lt;sup&amp;gt;43&amp;lt;/sup&amp;gt; verschiedene Schlüssel generieren, wenn 27 Zeichen erlaubt sind (Kleinbuchstaben und Leerzeichen). Die Grundannahme von Hashing ist jetzt, dass in jeder gegebenen Anwendung nur ein (kleiner) Teil der erlaubten Schlüssel tatsächlich verwendet wird. Man definiert eine Hashfunktion, die jeden Schlüssel auf eine natürliche Zahl im Bereich 0...(M-1) abbildet, wobei M viel kleiner als |U| ist. &lt;br /&gt;
;Definition einer Hashfunktion: &lt;br /&gt;
:::&amp;lt;math&amp;gt; f: U  \rightarrow [0, 1, \ldots, M-1] \subset \mathbb{N} &amp;lt;/math&amp;gt;&lt;br /&gt;
:::&amp;lt;math&amp;gt; f(u \in U) = h \in [0, 1, \ldots, M-1]&amp;lt;/math&amp;gt;&lt;br /&gt;
h wird als ''Hashwert'' von u bezeichnet. Da M &amp;lt; |U|, werden notwendigerweise einige Schlüssel auf dieselbe Zahl abgebildet. Man bezeichnet den Fall &amp;lt;math&amp;gt; f(u_1 \in U) = f(u_2 \in U) &amp;lt;/math&amp;gt; als ''Kollision'' zwischen den Schlüsseln u&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; und u&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Die '''Aufgabe''' besteht jetzt darin, ein Hash-Funktion zu entwerfen, die möglichst wenige Kollisionen hat. Hashfunktionen ähneln damit einem Zufallszahlengenerator, weil jede Zahl &amp;lt;math&amp;gt; h \in 0 \ldots (M-1) &amp;lt;/math&amp;gt; nach Möglichkeit mit gleicher Wahrscheinlichkeit herauskommen soll. Wird dieses Ziel erreicht, spricht man vom ''uniformen Hashing''.&lt;br /&gt;
&lt;br /&gt;
In der Regel ist aber nicht vorher bekannt, welche Schlüssel in einer Anwendung verwendet werden. Es kann deshalb immer vorkommen, dass die verwendete Schlüsselmenge sehr viele Kollisionen verursacht. Man sieht in der Tat leicht ein, dass für jede gegebene Hashfunktion ungünstige Schlüsselmengen &amp;lt;math&amp;gt; U_f \subset U&amp;lt;/math&amp;gt; existieren, bei denen es sehr viele Kollisionen gibt. Im ungünstigsten Fall könnte U&amp;lt;sub&amp;gt;f&amp;lt;/sub&amp;gt; so gewählt sein, dass f(U&amp;lt;sub&amp;gt;f&amp;lt;/sub&amp;gt;) = k = const. gilt. Ein Hacker, der die verwendete Hashfunktion kennt, kann z.B. U&amp;lt;sub&amp;gt;f&amp;lt;/sub&amp;gt; absichtlich so wählen, um eine denial-of-service-Attacke gegen einen hash-basierten Webservice zu starten. Ein anderes anschauliches Beispiel wäre eine Party, zu der nur Leute eingeladen werden, die an einem 8ten im Monat Geburtstag haben. Auf dieser Party ist es viel wahrscheinlicher, Leute zu finden, die am selben (oder gleichen) Tag Geburtstag haben, als wenn man alle einlädt.&lt;br /&gt;
&lt;br /&gt;
D.h. die Wahl einer guten Hashfunktion ist eine Kunst, und man muss (wenn möglich) die Daten analysieren um ein gutes f zu finden.&lt;br /&gt;
&lt;br /&gt;
====Perfektes Hashing====&lt;br /&gt;
&lt;br /&gt;
Kennt man die Untermenge der tatsächlich vorkommenden Schlüssel &amp;lt;math&amp;gt;U_f \subset U&amp;lt;/math&amp;gt; schon im voraus, hat man die Möglichkeit, eine ''perfekte Hashfunktion'' ohne Kollisionen zu entwerfen.&lt;br /&gt;
&lt;br /&gt;
;Beispiel anhand der Monatsnamen &lt;br /&gt;
&lt;br /&gt;
U ist in diesem Fall eine Menge von Strings der Länge 9 (weil der September als längster Monatsname 9 Zeichen hat). Es ergeben sich also &amp;lt;math&amp;gt;60^{9}&amp;lt;/math&amp;gt;&amp;gt;&amp;amp;asymp;10&amp;lt;sup&amp;gt;16&amp;lt;/sup&amp;gt;&amp;amp;asymp;2&amp;lt;sup&amp;gt;54&amp;lt;/sup&amp;gt; mögliche Strings, da mit Groß- und Kleinbuchstaben, Umlauten, ß und Leerzeichen 60 Zeichen im deutschen Alphabet vorhanden sind. Von all diesen Möglichkeiten werden genau 12 benutzt:&lt;br /&gt;
:::&amp;lt;math&amp;gt;U_f&amp;lt;/math&amp;gt; = {&amp;quot;Januar&amp;quot;; &amp;quot;Februar&amp;quot;; ... ; &amp;quot;Dezember&amp;quot;}&lt;br /&gt;
* Benutzt man nun als Hashfunktion die Anfangsbuchstaben der Monatsnamen, benötigt man dafür 6 bit. M ist somit 64. &lt;br /&gt;
:::{&amp;quot;Januar&amp;quot;; &amp;quot;Februar&amp;quot;; ... ; &amp;quot;Dezember&amp;quot;} &amp;amp;rarr; {&amp;quot;J&amp;quot;; &amp;quot;F&amp;quot;; &amp;quot;M&amp;quot;; &amp;quot;A&amp;quot;; &amp;quot;M&amp;quot;; &amp;quot;J&amp;quot;; &amp;quot;J&amp;quot;; &amp;quot;A&amp;quot;; &amp;quot;S&amp;quot;; &amp;quot;O&amp;quot;; &amp;quot;N&amp;quot;; &amp;quot;D&amp;quot;}&lt;br /&gt;
:Dabei enstehen viele Kollisionen (J wird 3x verwendet, M 2x, A 2x), die gewählte ist also keine gute Hashfunktion&lt;br /&gt;
* Benutzt man als Hashfunktion die ersten 3 Buchstaben benötigt man 18 bit, M = &amp;lt;math&amp;gt;2^{18}&amp;lt;/math&amp;gt; &lt;br /&gt;
:::{&amp;quot;Januar&amp;quot;; &amp;quot;Februar&amp;quot;; ... ; &amp;quot;Dezember&amp;quot;} &amp;amp;rarr; {&amp;quot;Jan&amp;quot;, &amp;quot;Feb&amp;quot;, &amp;quot;März&amp;quot;, &amp;quot;Apr&amp;quot;, &amp;quot;Mai&amp;quot;, &amp;quot;Jun&amp;quot;, &amp;quot;Jul&amp;quot;, &amp;quot;Aug&amp;quot;, &amp;quot;Sep&amp;quot;, &amp;quot;Okt&amp;quot;, &amp;quot;Nov&amp;quot;, &amp;quot;Dez&amp;quot;}&lt;br /&gt;
:Nun entstehen keine Kollision mehr. Diese Hashfunktion ist deshalb beim Ausfüllen von Formularen und dergleichen sehr beliebt. Dafür ist M aber recht groß.&lt;br /&gt;
&lt;br /&gt;
Die Aufgabe wird also präzisiert: man sucht für &amp;lt;math&amp;gt;U_f&amp;lt;/math&amp;gt; eine '''minimale, perfekte Hashfunktion''', für die &amp;lt;math&amp;gt;|U_f| = M&amp;lt;/math&amp;gt; gilt. Ein Verfahren hierfür ist Gegenstand von Übungsblatt 9.&lt;br /&gt;
&lt;br /&gt;
====Universelles Hashing====&lt;br /&gt;
&lt;br /&gt;
Hier wählt man für eine gegebene Hashtabelle die Hashfunktion per Zufallszahl aus einer (großen) Menge erlaubter Hashfunktion &amp;amp;rarr; Die Wahrscheinlichkeit, dass die Hashfunktion für die Schlüssel ungünstig ist, wird dadruch minimiert. Die oben erwähnte denial-of-service-Attacke ist jetzt nicht mehr möglich, weil kein Hacker die Hashfunktion im voraus kennen kann. Näheres zum universellen Hashing finden Sie in der [http://en.wikipedia.org/wiki/Universal_hashing Wikpedia].&lt;br /&gt;
&lt;br /&gt;
====Kryptographische Hashfunktionen====&lt;br /&gt;
&lt;br /&gt;
In kryptographischen Anwendungen treten neben dem Hauptziel, die Größe des Universums auf eine überschaubare Zahl von Integer-Werten zu reduzieren, zwei weitere Anforderungen, die für Verschlüsselung bzw. verschlüsselte Kommunikation wichtig sind: erstens will man Kollisionen unbedingt vermeiden (damit zwei verschiedene Dokumente oder Passwörter nicht auf den gleichen Hashwert abgebildet werden), und zweitens darf es nicht möglich sein, aus dem Hashwert die urpsrüngliche Nachricht (also das Dokument oder Passwort) zu rekonstruieren. Man wählt deshalb relative große M (128 bit und mehr) sowie spezielle, für diesen Zweck optimierte Hashfunktionen, wie z.B. [http://de.wikipedia.org/wiki/Message-Digest_Algorithm_5 md5] und [http://de.wikipedia.org/wiki/SHA1 sha1]. Weitere Einzelheiten finden Sie in der [http://en.wikipedia.org/wiki/Cryptographic_hash_function Wikipedia].&lt;br /&gt;
&lt;br /&gt;
====Beliebte Standard-Hashfunktionen====&lt;br /&gt;
&lt;br /&gt;
In der Praxis definiert man Hashfunktionen gewöhnlich zweistufig: Zunächst bildet man den Schlüssel auf einen 32 bit Integerwert ab, M' ist damit 2&amp;lt;sup&amp;gt;32&amp;lt;/sup&amp;gt;. Dieser &amp;quot;rohe&amp;quot; Hashwert wird dann mittels der Modulo-Operation auf die eigentliche Größe M des assoziativen Arrays abgebildet:&lt;br /&gt;
:::&amp;lt;math&amp;gt; f(u \in U) = f'(u \in U)\,\%\,M\,=\,h \in [0, 1, \ldots, M-1] &amp;lt;/math&amp;gt;&lt;br /&gt;
mit&lt;br /&gt;
:::&amp;lt;math&amp;gt; f'(u \in U) = h' \in [0, 1, \ldots, 2^{32}-1] &amp;lt;/math&amp;gt;&lt;br /&gt;
Der große Wert von M' sichert, dass man bei der Wahl von M großen Spielraum hat, so dass die Größe des assoziativen Arrays sehr gut an die Menge der zu speichernden Daten angepaßt werden kann. Die Funktion f'(u) definiert man wie folgt:&lt;br /&gt;
* Falls U = &amp;lt;tt&amp;gt;unsigned int&amp;lt;/tt&amp;gt; (32bit int Datentyp) &amp;amp;rArr; f'(u) = u&lt;br /&gt;
* Falls U = &amp;lt;tt&amp;gt;signed int&amp;lt;/tt&amp;gt; &amp;amp;rArr; Typkonvertierung nach &amp;lt;tt&amp;gt;unsigned int&amp;lt;/tt&amp;gt; &amp;amp;rArr; f'(u) = (unsigned int)u&lt;br /&gt;
* Andere Schlüsseltypen (also insbesondere Strings) interpretiert man als Array of byte &amp;amp;rArr; f'(u) konvertiert Array of Byte nach &amp;lt;tt&amp;gt;unsigned int&amp;lt;/tt&amp;gt;. Beispiele für solche Funktionen:&lt;br /&gt;
:: '''Bernsteinfunktion:'''&lt;br /&gt;
     def bHash(u):     # u: Array of Byte&lt;br /&gt;
         h=0&lt;br /&gt;
         for k in u:&lt;br /&gt;
             h = 33 * h + k &lt;br /&gt;
         return h&lt;br /&gt;
:: '''modifizierte Bernsteinfunktion:'''&lt;br /&gt;
     def mbHash(u):    # u: Array of Byte&lt;br /&gt;
         h=0&lt;br /&gt;
         for k in u:&lt;br /&gt;
             h = (33 * h) ^ k  # ^ ist bitweises Xor&lt;br /&gt;
         return h&lt;br /&gt;
:: '''Shift-Add-Xor-Funktion:'''&lt;br /&gt;
     def saxhash(u):   # u: Array of Byte&lt;br /&gt;
         h=0&lt;br /&gt;
         for k in u:&lt;br /&gt;
             h ^= (h &amp;lt;&amp;lt; 5) + (h &amp;gt;&amp;gt; 2) + k  # &amp;lt;&amp;lt; und &amp;gt;&amp;gt; sind Links- bzw. Rechtsshift der Bits, ^= ist bitweise Xor-Zuweisung&lt;br /&gt;
         return h&lt;br /&gt;
:: '''Fowler/Noll/Vo-Funktion:'''&lt;br /&gt;
     def FNVhash(u):   # u: Array of Byte&lt;br /&gt;
         h = 2166136261&lt;br /&gt;
         for k in u:&lt;br /&gt;
             h = (16777619 * h) ^ k   # ähnlich der modifizierten Bernsteinfunktion, aber mit anderen Konstanten&lt;br /&gt;
         return h&lt;br /&gt;
:: Die verwendeten Konstanten sind experimentell so gewählt worden, dass die Hashfunktionen in typischen Praxisanwendungen relativ wenige Kollisionen verursachen. Der tiefere Grund, warum z.B. 33 in der Bernsteinfunktion eine gute Wahl darstellt, ist unbekannt. Es empfielt sich, in einer gegebenen Anwendung mit mehreren Hashfunktionen zu experimentieren. Weitere solche Funktionen und andere nützliche Informationen findet man auf der Seite [http://www.eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx eternallyconfuzzled.com].&lt;br /&gt;
&lt;br /&gt;
== Hashtabellen ==&lt;br /&gt;
&lt;br /&gt;
Eine Hashtabelle ist eine Datenstruktur, die die Funktionalität des assoziativen Arrays mit Hilfe von Hashing realisiert. Das Grundprinzip besteht darin, dass die Hashtabelle intern ein (dynamisches) Array der Größe &amp;lt;tt&amp;gt;capacity&amp;lt;/tt&amp;gt; verwaltet, so dass die Hashwerte als Indizes in diesem Array verwendet werden können (&amp;lt;tt&amp;gt;capacity&amp;lt;/tt&amp;gt; entspricht der Zahl M aus der mathematischen Definition oben). Eine naive Implementation der Einfügeoperation sieht also so aus &lt;br /&gt;
     def __setitem__(self, key, value):    # naive Implementation, funktioniert so nicht&lt;br /&gt;
         index = self.hash(key) % self.capacity&lt;br /&gt;
         self.array[index] = value&lt;br /&gt;
Diese Implementation ist allerdings zu einfach. Wenn nämlich die Schlüssel aus dem Universum U beliebig gewählt werden dürfen, sind Kollisionen unvermeidlich. Tritt aber eine Kollision auf, werden die Daten eines Schlüssels mit den Daten eines anderen Schlüssels überschrieben. Um Kollisionen geschickt zu behandeln gibt es zwei Ansätze:&lt;br /&gt;
* lineare Verkettung&lt;br /&gt;
* offene Adressierung&lt;br /&gt;
&lt;br /&gt;
=== Hashtabelle mit linearer Verkettung (offenes Hashing/geschlossene Adressierung) ===&lt;br /&gt;
&lt;br /&gt;
Man kann dies als die pessimistische Lösung bezeichnen: Man nimmt an, dass Kollisionen häufig auftreten. Deshalb wird unter jedem&lt;br /&gt;
Hashindex gleich eine Liste angelegt, in der Einträge mit gleichem Hashindex aufgenommen werden können. Die Hashtabelle verwaltet ein Array von Listen, und jedes Arrayfeld kann beliebig viele Elemente speichern: Wird ein Element auf den Index &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt; abgebildet, werden die Daten einfach an die betreffende Liste angehängt. Bei Zugriff auf ein Element wird zunächst die passende Liste gesucht (mit Hilfe des Hashwerts), danach erfolgt in dieser Liste eine sequentielle Suche nach dem richtigen Schlüssel.&lt;br /&gt;
&lt;br /&gt;
Um diese Idee implementieren zu können, benötigen wir zunächst eine Hilfsklasse &amp;lt;tt&amp;gt;HashNode&amp;lt;/tt&amp;gt;, die (Schlüssel, Wert)-Paare speichert und mit Hilfe von &amp;lt;tt&amp;gt;next&amp;lt;/tt&amp;gt; eine verkettete Liste realisiert:&lt;br /&gt;
  class HashNode:&lt;br /&gt;
      def __init__(self,key,data,next):&lt;br /&gt;
          self.key = key&lt;br /&gt;
          self.data = data&lt;br /&gt;
          self.next = next    # Verkettung!&lt;br /&gt;
Die eigentliche Hashtabelle wird in der Klasse ''HashTable'' implementiert:&lt;br /&gt;
  class HashTable:&lt;br /&gt;
      def __init__(self):&lt;br /&gt;
         self.capacity = ... # Geeignete Werte siehe unten&lt;br /&gt;
         self.size = 0       # Anzahl der Werte, die zur Zeit tatsächlich gespeichert sind&lt;br /&gt;
         self.array = [None]*self.capacity&lt;br /&gt;
Wie oben bereits erwähnt, werden die Zugriffsoperatoren ''[ ]'' für eine Datenstruktur in Python durch die Funktionen &amp;lt;tt&amp;gt;__setitem__&amp;lt;/tt&amp;gt; bzw. &amp;lt;tt&amp;gt;__getitem__&amp;lt;/tt&amp;gt; implementiert. &lt;br /&gt;
Die &amp;lt;tt&amp;gt;__setitem__&amp;lt;/tt&amp;gt;-Funktion speichert die gegebenen Daten unter dem Schlüssel &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; in der &amp;lt;tt&amp;gt;HashTable&amp;lt;/tt&amp;gt;-Klasse:&lt;br /&gt;
     def __setitem__(self, key, value):&lt;br /&gt;
         index = hash(key) % self.capacity  # hash(...) ist in Python eine vordefinierte Funktion&lt;br /&gt;
         node  = self.array[index]          # finde die zu 'key' gehörende Liste&lt;br /&gt;
         while node is not None:            # sequentielle Suche nach 'key' in dieser Liste&lt;br /&gt;
             if node.key == key:&lt;br /&gt;
                 # Element 'key' ist schon in der Tabelle&lt;br /&gt;
                 # =&amp;gt; überschreibe die Daten mit dem neuen Wert&lt;br /&gt;
                 node.data = value&lt;br /&gt;
                 return&lt;br /&gt;
             # andernfalls: Kollision des Hashwerts, probiere nächsten 'key' aus&lt;br /&gt;
             node = node.next&lt;br /&gt;
         # kein Element hatte den richtigen Schlüssel.&lt;br /&gt;
         # =&amp;gt; es gibt diesen Schlüssel noch nicht&lt;br /&gt;
         #    füge also ein neues Element in die Hashtabelle ein&lt;br /&gt;
         self.array[index] = HashNode(key, value, self.array[index]) # der alte Anfang der Liste wird zum&lt;br /&gt;
                                                                     # Nachfolger des neu eingefügten ersten Elements&lt;br /&gt;
         self.size += 1&lt;br /&gt;
Die Funktion &amp;lt;tt&amp;gt;__getitem__&amp;lt;/tt&amp;gt; gibt die unter dem Schlüssel &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; abgelegten Daten zurück, oder eine Fehlermeldung, falls dieser Schlüssel nicht existiert:&lt;br /&gt;
     def __getitem__(self, key):&lt;br /&gt;
         index = hash(key) % self.capacity&lt;br /&gt;
         node = self.array[index]     # finde die zu 'key' gehörende Liste&lt;br /&gt;
         while node is not None:      # sequentielle Suche nach 'key' in dieser Liste&lt;br /&gt;
              if node.key == key:     # gefunden!&lt;br /&gt;
                  return node.data    # =&amp;gt; Daten zurückgeben&lt;br /&gt;
              node = node.next        # nächsten Schlüssel probieren&lt;br /&gt;
         raise KeyError(key)          # Schlüssel nicht gefunden =&amp;gt; Fehler&lt;br /&gt;
&lt;br /&gt;
==== Komplexität der linearen Verkettung und Wahl der Kapazität ====&lt;br /&gt;
&lt;br /&gt;
Die Komplexität wird durch zwei Operationen bestimmt: erstens das Auffinden der zu einem Schlüssel gehörenden Liste (die in O(1) erfolgt), zweitens das sequentielle Durchsuchen der Liste, die Zeit in O(L) erfordert, wobei L die mittlere Länge der Listen ist. Die Hashtabelle ist also nur schnell, wenn die Länge der Listen möglichst klein ist. Unter der Annahme des ''uniformen Hashings'', wenn also alle Indizes gleich häufig verwendet werden, ist L gleich dem '''Füllstand''' der Hashtabelle:&lt;br /&gt;
:::&amp;lt;math&amp;gt;\alpha = \frac{N}{M} = \frac{\text{size}}{\text{capacity}}&amp;lt;/math&amp;gt; wobei N die Größe &amp;lt;tt&amp;gt;size&amp;lt;/tt&amp;gt; der Hashtabelle und M die Größe &amp;lt;tt&amp;gt;capacity&amp;lt;/tt&amp;gt; des Arrays ist.&lt;br /&gt;
Wenn die Hashwerte uniform sind, entfallen auf jede Liste im Mittel N/M Einträge (N Einträge, verteilt auf M Listen). Die Gesamtkomplexität berechnet sich nach der Sequenzregel zu&lt;br /&gt;
:::&amp;lt;math&amp;gt;O(1+\alpha)&amp;lt;/math&amp;gt;&lt;br /&gt;
Für eine effiziente Suche muss demnach &amp;lt;math&amp;gt;\alpha \in O(1)&amp;lt;/math&amp;gt; gewählt werden. Dies erreicht man, indem man, wie beim dynamischen Array, &amp;lt;tt&amp;gt;capacity&amp;lt;/tt&amp;gt; immer wieder anpasst, falls &amp;lt;tt&amp;gt;size&amp;lt;/tt&amp;gt; zu groß wird. Üblicherweise verdoppelt man &amp;lt;tt&amp;gt;capacity&amp;lt;/tt&amp;gt;, sobald &amp;lt;tt&amp;gt;size == capacity&amp;lt;/tt&amp;gt; erreicht wird. Analog zum dynamischen Array werden die Daten dann aus dem alten Array (&amp;lt;tt&amp;gt;self.array&amp;lt;/tt&amp;gt;) in ein entsprechend vergrößertes neues Array kopiert.&lt;br /&gt;
&lt;br /&gt;
In der C++ Standardbibliothek (Klasse &amp;lt;tt&amp;gt; [http://www.sgi.com/tech/stl/hash_map.html std::hash_map]&amp;lt;/tt&amp;gt;, siehe auch [http://gcc.gnu.org/viewcvs/*checkout*/trunk/libstdc++-v3/src/hashtable.cc GCC hashtable.cc (Primzahlen)] und [http://gcc.gnu.org/viewcvs/*checkout*/trunk/libstdc++-v3/include/tr1_impl/hashtable_policy.h GCC Hash Implementation]) wird die Hashtabelle häufig so&lt;br /&gt;
implementiert. Dabei wird &amp;lt;tt&amp;gt;capacity&amp;lt;/tt&amp;gt; immer als ''Primzahl'' gewählt, wobei sich aufeinanderfolgende Kapazitäten immer ungefähr verdoppeln:&lt;br /&gt;
  53, 97, 193, 398, 769, ...&lt;br /&gt;
Die Wahl von Primzahlen hat zur Folge, dass &amp;lt;tt&amp;gt;hash(key) % self.capacity&amp;lt;/tt&amp;gt; ''alle'' Bits von h benutzt (Eigenschaft aus der Zahlentheorie). Die Kapizität wird vergrößert, wenn &amp;lt;tt&amp;gt;size == capacity&amp;lt;/tt&amp;gt; erreicht wird, und die ungefähre Verdoppelung sichert, dass die amortisierte Komplexität der Einfügeoperation in O(1) ist (wie beim dynamischen Array).&lt;br /&gt;
&lt;br /&gt;
=== Hashtabelle mit offener Adressierung (geschlossenes Hashing) ===&lt;br /&gt;
[[Image:HASHTB12.svg.png|frame|Prinzip ([http://en.wikipedia.org/wiki/Hash_table Quelle])]]&lt;br /&gt;
&lt;br /&gt;
Dies kann als die optimistische Variante betrachtet werden: man nimmt an, dass Kollisionen nicht so häufig auftreten, um eine komplexe Datenstruktur wie das &amp;quot;Array von Listen&amp;quot; zu rechtfertigen. Stattdessen behandelt man Kollisionen mit einer einfachen '''Idee''': Wenn &amp;lt;tt&amp;gt;array[index]&amp;lt;/tt&amp;gt; durch Kollision bereits vergeben ist, probiere einen&lt;br /&gt;
anderen Index aus (siehe auch [http://de.wikipedia.org/wiki/Hashtabelle#Hashing_mit_offener_Adressierung Wikipedia (de)] und&lt;br /&gt;
[http://en.wikipedia.org/wiki/Open_addressing Wikipedia (en)]). Dabei muss man folgendes beachten:&lt;br /&gt;
&lt;br /&gt;
* Das Array enthält pro Element höchstens ein (key,value)-Paar&lt;br /&gt;
* Das Array muss stets mindestens ''einen'' freien Platz haben (sonst gäbe es beim Ausprobieren anderer Indizes eine Endlosschleife). Es gilt immer &amp;lt;tt&amp;gt;self.size &amp;lt; self.capacity&amp;lt;/tt&amp;gt;. Dies war bei der vorigen Hash-Implementation mit linearer Verkettung nicht notwendig (aber im Sinne schneller Zugriffszeiten trotzdem wünschenswert).&lt;br /&gt;
&lt;br /&gt;
==== Vorgehen bei Kollisionen ====&lt;br /&gt;
&lt;br /&gt;
=====Sequentielles Sondieren=====&lt;br /&gt;
&lt;br /&gt;
Probiere den nächsten Index: &amp;lt;tt&amp;gt;index = (index+1) % capacity&amp;lt;/tt&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Vorteil: einfach&lt;br /&gt;
* Nachteil: Clusterbildung&lt;br /&gt;
&lt;br /&gt;
Clusterbildung heißt, dass sich größere zusammenhängende Bereiche bilden die belegt sind, unterbrochen von Bereichen die komplett frei sind. Beim Versuch des Einfügens eines Elements an einen Platz, der schon belegt ist, muss jetzt das ganze Cluster sequentiell durchlaufen werden, bis ein freier Platz gefunden wird. Damit entspricht die Komplexität der Suche der mittleren Länge der belegten Bereiche, was sich entsprechend in einer langsamen Suche widerspiegelt.&lt;br /&gt;
&lt;br /&gt;
=====Doppeltes Hashing=====&lt;br /&gt;
&lt;br /&gt;
[http://de.wikipedia.org/wiki/Doppel-Hashing Wikipedia (de)] [http://en.wikipedia.org/wiki/Double_hashing Wikipedia (en)]&lt;br /&gt;
&lt;br /&gt;
Bestimme einen neuen Index (bei Kollisionen) durch eine ''2. Hashfunktion''.&lt;br /&gt;
&lt;br /&gt;
Das doppelte Hashing wird typischerweise in der Praxis angewendet und liegt auch der Python Implementierung des Datentyps [http://docs.python.org/tut/node7.html#SECTION007500000000000000000 Dictionary] (Syntax &amp;lt;tt&amp;gt;{'a':1, 'b':2, 'c':3}&amp;lt;/tt&amp;gt; zugrunde.&lt;br /&gt;
&lt;br /&gt;
Eine effiziente Implementierung dieses Datentyps ist für die Performance der Skriptsprache Python extrem wichtig, da z.B. beim Aufruf einer Funktion der auszuführunde Code in einem Dictionary unter dem Schlüssel ''Funktionsname'' nachgeschlagen wird oder die Werte lokaler Variablen innerhalb einer Funktion ebenfalls in einem Dictionary zu finden sind.&lt;br /&gt;
&lt;br /&gt;
Für die Implementierung in Python werden wieder die obigen Klassen &amp;lt;tt&amp;gt;HashNode&amp;lt;/tt&amp;gt; (das Attribut &amp;lt;tt&amp;gt;next&amp;lt;/tt&amp;gt; kann allerdings jetzt entfernt werden) und &amp;lt;tt&amp;gt;HashTable&amp;lt;/tt&amp;gt; benötigt, es folgen die angepassten Implementationen von &amp;lt;tt&amp;gt;__setitem__&amp;lt;/tt&amp;gt; und &amp;lt;tt&amp;gt;__getitem__&amp;lt;/tt&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
  def __setitem__(self, key, value):&lt;br /&gt;
      h = hash(key)&lt;br /&gt;
      index = h % self.capacity&lt;br /&gt;
      while True:&lt;br /&gt;
          if self.array[index] is None or self.array[index].key is None:&lt;br /&gt;
               # das Feld ist frei (1. Abfrage)&lt;br /&gt;
               # oder das Feld ist als frei markiert (2. Abfrage)&lt;br /&gt;
               self.array[index] = HashNode(key, value)&lt;br /&gt;
               self.size +=1&lt;br /&gt;
               return&lt;br /&gt;
          if self.array[index].key == key:&lt;br /&gt;
               # Es gibt diesen Schlüssel schon,&lt;br /&gt;
               # überschreibe die Daten&lt;br /&gt;
               self.array[index].data = value&lt;br /&gt;
               return&lt;br /&gt;
          # Letzter Fall: Kollision =&amp;gt; neuer Index durch 2. Hashfunktion&lt;br /&gt;
          index = (5*index+1+h) % self.capacity&lt;br /&gt;
          h = h &amp;gt;&amp;gt; 5&lt;br /&gt;
(für den &amp;lt;tt&amp;gt;&amp;gt;&amp;gt;&amp;lt;/tt&amp;gt;-Operator, siehe die [http://docs.python.org/ref/shifting.html Python Dokumentation])&lt;br /&gt;
&lt;br /&gt;
Die vorgestellte Implementierung orientiert sich an Pythons interner Dictionary Implementierung, der zugehörige Quelltext (mit ausführlichem Kommentar) findet sich unter [http://svn.python.org/view/*checkout*/python/trunk/Objects/dictobject.c dictobject.c Python Implementation (SVN)]&lt;br /&gt;
&lt;br /&gt;
===== Beispiel für doppeltes Hashing =====&lt;br /&gt;
&lt;br /&gt;
Der Übersichtlichkeit wegen wählen wir M'=2&amp;lt;sup&amp;gt;5&amp;lt;/sup&amp;gt; (statt 2&amp;lt;sup&amp;gt;32&amp;lt;/sup&amp;gt;) und eine Kapazität von M=8.&lt;br /&gt;
&lt;br /&gt;
Roher Hashwert (für das Beispiel willkürlich gewählt):&lt;br /&gt;
  h=25&lt;br /&gt;
Erster Index:&lt;br /&gt;
  i0 = h % capacity = 25 % 8 = 1&lt;br /&gt;
Es finde eine Kollision statt. Es wird ein zweiter Index berechnet:&lt;br /&gt;
  i1 = (5*i0 + 1 + h) % 8 = (5*1 + 1 + 25) % 8 = 31 % 8 = 7&lt;br /&gt;
Der Hashwert wird aktualisiert um die höherwertigen Bits von &amp;lt;tt&amp;gt;h&amp;lt;/tt&amp;gt; ins Spiel zu bringen (hier durch &amp;lt;tt&amp;gt;h &amp;gt;&amp;gt; 2&amp;lt;/tt&amp;gt; anstelle von &amp;lt;tt&amp;gt;h &amp;gt;&amp;gt; 5&amp;lt;/tt&amp;gt; im originalen Pythoncode). Wir stellen &amp;lt;tt&amp;gt;h&amp;lt;/tt&amp;gt; als Binärzahl dar, damit der Rechtsshift besser sichtbar wird:&lt;br /&gt;
  h = h &amp;gt;&amp;gt; 2 &lt;br /&gt;
  ==&amp;gt; h = (11001 &amp;gt;&amp;gt; 2) = 00110 = 6&lt;br /&gt;
Es finde wieder eine Kollision statt, so dass ein dritter Index berechnet werden muss.&lt;br /&gt;
  i2 = (5*i1 + 1 + h) % 8 = (5*7 + 1 + 6) % 8 = 42 % 8 = 2&lt;br /&gt;
Der Hashwert wird wiederum aktualisiert:&lt;br /&gt;
  h = h &amp;gt;&amp;gt; 2&lt;br /&gt;
  ==&amp;gt; h = (00110 &amp;gt;&amp;gt; 2) = 00001 = 1&lt;br /&gt;
Es finde eine Kollision statt, und wir berechnen den vierten Index:&lt;br /&gt;
  i3 = (5*i2 + 1 + h) % 8 = (5*2 + 1 + 1) % 8 = 12 % 8 = 4&lt;br /&gt;
Der Hashwert wird nochmals aktualisiert und erreicht jetzt den Wert 0 (der sich dann nicht mehr ändert):&lt;br /&gt;
  h = h &amp;gt;&amp;gt; 2 &lt;br /&gt;
  ==&amp;gt; h = (00110 &amp;gt;&amp;gt; 2) = 0&lt;br /&gt;
Es finde eine Kollision statt. Da jetzt &amp;lt;tt&amp;gt;h = 0&amp;lt;/tt&amp;gt; gilt, und die Zahlen 5 (Multiplikator) und 8 (capacity) teilerfremd sind, werden ab jetzt systematisch alle Indizes von 0 bis 7 durchprobiert (in der durch die Modulo-Operation bestimmten Reihenfolge):&lt;br /&gt;
  i4  = (5*i3  + 1 + h) % 8 = (5*4 + 1 + 0) % 8 = 21 % 8 = 5&lt;br /&gt;
  i5  = (5*i4  + 1 + h) % 8 = (5*5 + 1 + 0) % 8 = 26 % 8 = 2&lt;br /&gt;
  i6  = (5*i5  + 1 + h) % 8 = (5*2 + 1 + 0) % 8 = 11 &amp;amp; 8 = 3&lt;br /&gt;
  i7  = (5*i6  + 1 + h) % 8 = (5*3 + 1 + 0) % 8 = 16 &amp;amp; 8 = 0&lt;br /&gt;
  i8  = (5*i7  + 1 + h) % 8 = (5*0 + 1 + 0) % 8 =  1 &amp;amp; 8 = 1&lt;br /&gt;
  i9  = (5*i8  + 1 + h) % 8 = (5*1 + 1 + 0) % 8 =  6 &amp;amp; 8 = 6&lt;br /&gt;
  i10 = (5*i9  + 1 + h) % 8 = (5*6 + 1 + 0) % 8 = 31 &amp;amp; 8 = 7&lt;br /&gt;
  i11 = (5*i10 + 1 + h) % 8 = (5*7 + 1 + 0) % 8 = 36 &amp;amp; 8 = 4&lt;br /&gt;
Allen Indizes werden also erreicht, bevor sich die Folge wiederholt. Da man &amp;lt;tt&amp;gt;capacity&amp;lt;/tt&amp;gt; immer so wählt, dass mindestens ein Arrayfeld noch frei ist, wird dadurch immer ein geeigneter Platz für das einzufügende Element gefunden.&lt;br /&gt;
&lt;br /&gt;
==== Komplexität der offenen Adressierung ====&lt;br /&gt;
&lt;br /&gt;
* Annahme: uniformes Hashing, das heißt alle Indizes haben gleiche Wahrscheinlichkeit&lt;br /&gt;
* Füllstand &amp;lt;math&amp;gt;\alpha =\frac{N}{M} = \frac{\text{size}}{\text{capacity}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* '''Erfolglose Suche''' (d.h. es wird entweder ein neues Element eingefügt oder ein &amp;lt;tt&amp;gt;KeyError&amp;lt;/tt&amp;gt; geworfen): Untere Schranke für die Komplexität ist &amp;lt;math&amp;gt;\Omega\left(\frac{1}{1-\alpha}\right)&amp;lt;/math&amp;gt; Schritte (= Anzahl der notwendigen index-Berechnungen).&lt;br /&gt;
* '''Erfolgreiche Suche''' &amp;lt;math&amp;gt;\Omega\left(\frac{1}{\alpha}\ln\left(\frac{1} {1-\alpha}\right)\right)&amp;lt;/math&amp;gt; Schritte.&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;0&amp;quot; cellpadding=&amp;quot;5&amp;quot; align=&amp;quot;center&amp;quot;&lt;br /&gt;
! &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;&lt;br /&gt;
! 0.5&lt;br /&gt;
! 0.9&lt;br /&gt;
|- &lt;br /&gt;
| erfolglos&lt;br /&gt;
| 2.0&lt;br /&gt;
| 10&lt;br /&gt;
|-&lt;br /&gt;
| erfolgreich&lt;br /&gt;
| 1.4&lt;br /&gt;
| 2.6&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==== Wahl der Kapazität ====&lt;br /&gt;
Man sieht an der obigen Tabelle, dass die erfolglose Suche (und damit das Einfügen) sehr langsam wird, wenn der Füllstand hoch ist. In Python wird &amp;lt;tt&amp;gt;capacity&amp;lt;/tt&amp;gt; deshalb so gewählt, dass &amp;lt;math&amp;gt;\alpha \leq 2/3&amp;lt;/math&amp;gt;. Falls &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt; größer werden sollte, verdopple die Kapazität und kopiere das alte array in das neue Array (analog zum dynamischen Array)&lt;br /&gt;
&lt;br /&gt;
In Python werden die Kapazitätsgrößen als Zweierpotenzen gewählt, also 4,8,16,32,...,&lt;br /&gt;
so dass &amp;lt;tt&amp;gt;h % self.capacity&amp;lt;/tt&amp;gt; nur die unteren Bits von &amp;lt;tt&amp;gt;h&amp;lt;/tt&amp;gt; benutzt. Die oberen Bits von &amp;lt;tt&amp;gt;h&amp;lt;/tt&amp;gt; kommen erst ins Spiel, wenn bei der Berechnung der 2. Hashfunktion die Aktualisierung &amp;lt;tt&amp;gt;h = h &amp;gt;&amp;gt; 5&amp;lt;/tt&amp;gt; erfolgt. Dies hat sich bei umfangreichen Experimenten als sehr gut Lösung erwiesen.&lt;br /&gt;
&lt;br /&gt;
== Anwendung von Hashing ==&lt;br /&gt;
&lt;br /&gt;
* Hashtabelle, assoziatives Aray&lt;br /&gt;
* Sortieren in linearer Zeit (Übungsaufgabe 6.2)&lt;br /&gt;
* Suchen von Strings in Texten: Rabin-Karp-Algorithmus&lt;br /&gt;
* ...&lt;br /&gt;
&lt;br /&gt;
=== Rabin Karp Algorithmus ===&lt;br /&gt;
[http://de.wikipedia.org/wiki/Rabin-Karp-Algorithmus Wikipedia (de)] [http://en.wikipedia.org/wiki/Rabin-Karp_string_search_algorithm Wikipedia (en)]&lt;br /&gt;
&lt;br /&gt;
In Textverarbeitungsanwendungen ist eine häufig benutzte Funktion die ''Search &amp;amp; Replace'' Funktionalität. Die Suche sollte in O(len(text)) möglich sein, aber ein naiver Algorithmus braucht O(len(text)*len(searchstring))&lt;br /&gt;
&lt;br /&gt;
==== Naive Implementierung der Textsuche ====&lt;br /&gt;
  def search(text, s):&lt;br /&gt;
      M, N = len(text), len(s)&lt;br /&gt;
      for k in range(M-N):&lt;br /&gt;
          if s==text[k:k+N]:   # O(N), da N Zeichen verglichen werden müssen&lt;br /&gt;
              return k&lt;br /&gt;
      return -1 #nicht gefunden&lt;br /&gt;
&lt;br /&gt;
==== Idee des Rabin Karp Algorithmus ====&lt;br /&gt;
Statt Vergleichen &amp;lt;tt&amp;gt;s==text[k:k+N]&amp;lt;/tt&amp;gt;, die O(N) benötigen da N Vergleiche der Buchstaben durchgeführt werden müssen, Vergleiche die Hashs von Suchstring und dem zu untersuchenden Textabschnitt: &amp;lt;tt&amp;gt;hash(s) == hash(text[k:k+N])&amp;lt;/tt&amp;gt;. Dabei muss natürlich &amp;lt;tt&amp;gt;hash(s)&amp;lt;/tt&amp;gt; nur einmal berechnet werden, wohingegen &amp;lt;tt&amp;gt;hash(text[k:k+N])&amp;lt;/tt&amp;gt; immer wieder neu berechnet werden muss. Damit der Vergleich O(1) sein kann, ist es deswegen erforderlich, eine solche Hashfunktion zu haben, die nicht alle Zeichen (das wäre O(N) ) einlesen muss, sondern die vorhergehende Hashfunktion mit einbezieht.&lt;br /&gt;
&lt;br /&gt;
Eine solche Hashfunktion heißt ''Running Hash'' und funktioniert analog zum ''Sliding Mean''.&lt;br /&gt;
&lt;br /&gt;
Die Running Hash Funktion berechnet in O(1) den hash von &amp;lt;tt&amp;gt;text[k+1:k+1+N]&amp;lt;/tt&amp;gt; ausgehend vom hash für &amp;lt;tt&amp;gt;text[k:k+N]&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Idee: Interpretiere den Text als Ziffern in einer base d Darstellung:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;h_k = \text{text}[k]\cdot d^{N-1} + \text{text}[k+1]\cdot d^{N-2} + \cdots + \text{text}[k+N-1]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Für die Basis 10 (Dezimalsystem) ergibt sich also&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;h_k = \text{text}[k]\cdot {10}^{N-1} + \text{text}[k+1]\cdot {10}^{N-2} + \cdots + \text{text}[k+N-1]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Daraus folgt&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;h_{k+1} = 10 \cdot h_k - \text {text}[k]\cdot {10}^{N} + \text {text}[k+N]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Die Komplexität dieses Updates ist O(1), falls man &amp;lt;math&amp;gt;{10}^{N}&amp;lt;/math&amp;gt; vorberechnet hat.&lt;br /&gt;
&lt;br /&gt;
In der Realität wählt man dann d=32 und benutzt noch an einigen Stellen modulo Operationen, um die Zahlen nicht zu groß werden zu lassen.&lt;br /&gt;
&lt;br /&gt;
==== Implementation ====&lt;br /&gt;
  def searchRabinKarp(text, s):&lt;br /&gt;
      ht, hs, dN = 0, 0, 1&lt;br /&gt;
      M, N = len(text), len(s)&lt;br /&gt;
      d, q = 32, 33554393&lt;br /&gt;
      #q ist eine große Primzahl, aber so,&lt;br /&gt;
      #dass d*q &amp;lt; 2**32 (um Überlauf zu vermeiden)&lt;br /&gt;
      &lt;br /&gt;
      #Initialisierung      &lt;br /&gt;
      for k in range(N):&lt;br /&gt;
          ht = (ht*d + ord(text[k])) % q&lt;br /&gt;
          #ord() gibt die ASCIInummer des übergebenen Zeichens zurück&lt;br /&gt;
          hs = (hs*d + ord( s[k] )) % q&lt;br /&gt;
          dN = (dN*d) % q&lt;br /&gt;
      #Die Variablen sind jetzt wie folgt initialisiert:&lt;br /&gt;
      #ht = hash(text[0:N])&lt;br /&gt;
      #hs = hash(s)&lt;br /&gt;
      #dN = (d**N) % q&lt;br /&gt;
      &lt;br /&gt;
      #Hauptschleife&lt;br /&gt;
      k = 0 &lt;br /&gt;
      while k &amp;lt; M-N:&lt;br /&gt;
          if hs == ht and s==text[k:k+N]:&lt;br /&gt;
              return k # search string an Position k gefunden&lt;br /&gt;
          if k+N &amp;lt; M:&lt;br /&gt;
              ht = (d*ht - dN * ord(text[k]) + dN*q + ord(text[k+N]) ) % q&lt;br /&gt;
          k +=1&lt;br /&gt;
      return -1  # search string nicht gefunden&lt;br /&gt;
&lt;br /&gt;
[[Iteration versus Rekursion|Nächstes Thema]]&lt;/div&gt;</summary>
		<author><name>Jschleic</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Hashing_und_assoziative_Arrays&amp;diff=4731</id>
		<title>Hashing und assoziative Arrays</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Hashing_und_assoziative_Arrays&amp;diff=4731"/>
				<updated>2010-08-10T14:57:47Z</updated>
		
		<summary type="html">&lt;p&gt;Jschleic: /* Komplexität der linearen Verkettung und Wahl der Kapazität */ typo&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Die Mitschrift gibts auch als [http://hci.iwr.uni-heidelberg.de/alda/images/AlDa.pdf PDF].&lt;br /&gt;
&lt;br /&gt;
== Assoziative Arrays ==&lt;br /&gt;
Assoziative Arrays werden genau wie gewöhnliche  Arrays benutzt, sie unterstützen also den lesenden und schreibenden Zugriff über einen Index i&lt;br /&gt;
    X = a[i]&lt;br /&gt;
    a[i] = x&lt;br /&gt;
Im Unterschied zum gewöhnlichen Array, wo i ein Integer im Bereich &amp;lt;math&amp;gt; i \in 0 \ldots N-1&amp;lt;/math&amp;gt; sein muss, kann der Typ von i jetzt ''beliebig'' sein. Eine typische Anwendung ist ein Wörterbuch&lt;br /&gt;
    x = toEnglish['Baum']   # ergibt 'tree'&lt;br /&gt;
In diesem Fall ist der Typ des Index &amp;lt;tt&amp;gt;string&amp;lt;/tt&amp;gt;, was in der Praxis der häufigste Fall ist, weshalb assoziative Arrays oft als ''Dictionary'' bezeichnet werden (so auch in Python, hier heißt der Typ &amp;lt;tt&amp;gt;dict&amp;lt;/tt&amp;gt;). Im allgemeinen kann aber jeder Typ als Index benutzt werden, für den entweder eine Ordnung oder eine Hashfunktion definiert ist. In erstem Fall realisiert man das assoziative Array mit Hilfe eines Suchbaums, in zweiten Fall mit Hilfe einer Hashtabelle.&lt;br /&gt;
&lt;br /&gt;
==Realisierung des assoziativen Arrays als Suchbaum== &lt;br /&gt;
&lt;br /&gt;
Wenn für den Indextyp des assoziativen Arrays eine Ordnung definiert ist (wenn also &amp;lt;tt&amp;gt;i1 &amp;lt; i2&amp;lt;/tt&amp;gt; oder &amp;lt;tt&amp;gt;cmp(i1, i2)&amp;lt;/tt&amp;gt; unterstützt werden), kann man das Indexierungsproblem auf das Suchproblem zurückführen, indem man die Indizes als Suchschlüssel verwendet. Im einfachsten Fall kann dies mit sequentieller Suche realisiert werden: Man verwendet ein gewöhnliches Array, dessen Einträge (Schlüssel, Daten)-Paare sind. Bei der Frage nach einem Schlüssel wird das betreffende Paar gesucht und die darin gespeicherten Daten zurückgegeben. Dies erfordert aber einen Suchaufwand in O(n). Effizienter geht es mit einem Suchbaum. Die Datenstruktur des Suchbaums muss dafür so erweitert werden, dass zu jedem Schlüssel (=Index) weitere Informationen gespeichert werden können (nämlich der Inhalt des indexierten Feldes). Man erweitert die Node-Klasse um ein Feld &amp;quot;data&amp;quot;:&lt;br /&gt;
    class Node:&lt;br /&gt;
        def __init__(self, key, data = None):&lt;br /&gt;
            self.key = key&lt;br /&gt;
            self.data = data&lt;br /&gt;
            self.left = self.right = None&lt;br /&gt;
Dann kann man eine Klasse &amp;lt;tt&amp;gt;AssociativeArray&amp;lt;/tt&amp;gt; realisieren, die die Indexoperationen intern mit Hilfe der Baumsuche implementiert. In Python wird der Zugriffsoperator ''[ ]'' für eine Datenstruktur (also innerhalb einer Klasse) wie folgt implementiert (vgl. die [http://docs.python.org/ref/sequence-types.html Python Docs zum Thema]):&lt;br /&gt;
        def __setitem__(self, key, value)&lt;br /&gt;
so dass im Programmtext dann folgende Syntax möglich ist: &amp;lt;tt&amp;gt;a[key] = value&amp;lt;/tt&amp;gt; (schreibender Zugriff auf &amp;lt;tt&amp;gt;a[key]&amp;lt;/tt&amp;gt;).&lt;br /&gt;
Analog wird der lesende Zugriff &amp;lt;tt&amp;gt;value = a[key]&amp;lt;/tt&amp;gt; wie folgt umgesetzt:&lt;br /&gt;
        def __getitem__(self, key, value)&lt;br /&gt;
Der Konstruktor der Klasse &amp;lt;tt&amp;gt;AssociativeArray&amp;lt;/tt&amp;gt; initialisiert einen leeren Suchbaum:&lt;br /&gt;
    class AssociativeArray:&lt;br /&gt;
        def __init__(self):&lt;br /&gt;
            self.root = None&lt;br /&gt;
            self.size = 0&lt;br /&gt;
Die Funktion &amp;lt;tt&amp;gt;__setitem__&amp;lt;/tt&amp;gt; schaut nach, ob ein Eintrag mit dem betreffenden Index bereits existiert. Wenn ja, werden seine Daten mit den neuen Daten überschrieben, andernfalls wird ein neuer Eintrag angelegt. Intern werden dazu die bereits bekannten Funktionen &amp;lt;tt&amp;gt;treeSearch&amp;lt;/tt&amp;gt; und &amp;lt;tt&amp;gt;treeInsert&amp;lt;/tt&amp;gt; verwendet (siehe Abschnitt [[Suchen#Suchbäume|Suchbäume]]):&lt;br /&gt;
        def __setitem__(self, index, data):&lt;br /&gt;
             node = treeSearch(self.root, index)&lt;br /&gt;
             if node is None:&lt;br /&gt;
                 self.root = treeInsert(self.root, index)&lt;br /&gt;
                 self.size += 1&lt;br /&gt;
                 node = treeSearch(self.root, index) &lt;br /&gt;
             node.data = data&lt;br /&gt;
(Eine geschicktere Implementation würde natürlich die wiederholten Aufrufe von &amp;lt;tt&amp;gt;treeSearch&amp;lt;/tt&amp;gt; und &amp;lt;tt&amp;gt;treeInsert&amp;lt;/tt&amp;gt; eliminieren. Dies ändert aber nichts an der Komplexität der Funktion.) Die Funktion &amp;lt;tt&amp;gt;__getitem__&amp;lt;/tt&amp;gt; sucht ebenfalls einen Eintrag mit dem gegebenen Index. Wenn er gefunden wird, gibt sie die zugehörigen Daten zurück, andernfalls eine Fehlermeldung:&lt;br /&gt;
        def __getitem__(self, index):&lt;br /&gt;
            node = treeSearch(self.root, index)&lt;br /&gt;
            if node is None:&lt;br /&gt;
                raise KeyError(index)&lt;br /&gt;
            else:&lt;br /&gt;
                return node.data&lt;br /&gt;
Die Indexoperationen haben bei der Realisierung mit Baumsuche eine Komplexität in O(log n).&lt;br /&gt;
&lt;br /&gt;
Ein wichtiges Beispiel für ein assoziatives Array, das auf diese Weise realisiert wurde, ist die C++ Standardklasse &amp;lt;tt&amp;gt;[http://www.sgi.com/tech/stl/Map.html std::map]&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Hashing ==&lt;br /&gt;
&lt;br /&gt;
Nun stellt sich die Frage, ob die Suche in einem assoziativen Array auch schneller, möglicherweise sogar in O(1), geht. Die Antwort lautet: Ja, wenn für die Indexklasse eine Hashfunktion definiert ist.&lt;br /&gt;
&lt;br /&gt;
===Hashfunktionen===&lt;br /&gt;
&lt;br /&gt;
Gegeben sei ein Universum U, dass die Menge aller legalen Schlüssel darstellt. Die Mächtigkeit |U| der Menge U ist im allgemeinen sehr groß. Beispielsweise kann man mit Strings der Länge 9 bis zu 27&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt;&amp;amp;asymp;10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt;&amp;amp;asymp;2&amp;lt;sup&amp;gt;43&amp;lt;/sup&amp;gt; verschiedene Schlüssel generieren, wenn 27 Zeichen erlaubt sind (Kleinbuchstaben und Leerzeichen). Die Grundannahme von Hashing ist jetzt, dass in jeder gegebenen Anwendung nur ein (kleiner) Teil der erlaubten Schlüssel tatsächlich verwendet wird. Man definiert eine Hashfunktion, die jeden Schlüssel auf eine natürliche Zahl im Bereich 0...(M-1) abbildet, wobei M viel kleiner als |U| ist. &lt;br /&gt;
;Definition einer Hashfunktion: &lt;br /&gt;
:::&amp;lt;math&amp;gt; f: U  \rightarrow [0, 1, \ldots, M-1] \subset \mathbb{N} &amp;lt;/math&amp;gt;&lt;br /&gt;
:::&amp;lt;math&amp;gt; f(u \in U) = h \in [0, 1, \ldots, M-1]&amp;lt;/math&amp;gt;&lt;br /&gt;
h wird als ''Hashwert'' von u bezeichnet. Da M &amp;lt; |U|, werden notwendigerweise einige Schlüssel auf dieselbe Zahl abgebildet. Man bezeichnet den Fall &amp;lt;math&amp;gt; f(u_1 \in U) = f(u_2 \in U) &amp;lt;/math&amp;gt; als ''Kollision'' zwischen den Schlüsseln u&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; und u&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Die '''Aufgabe''' besteht jetzt darin, ein Hash-Funktion zu entwerfen, die möglichst wenige Kollisionen hat. Hashfunktionen ähneln damit einem Zufallszahlengenerator, weil jede Zahl &amp;lt;math&amp;gt; h \in 0 \ldots (M-1) &amp;lt;/math&amp;gt; nach Möglichkeit mit gleicher Wahrscheinlichkeit herauskommen soll. Wird dieses Ziel erreicht, spricht man vom ''uniformen Hashing''.&lt;br /&gt;
&lt;br /&gt;
In der Regel ist aber nicht vorher bekannt, welche Schlüssel in einer Anwendung verwendet werden. Es kann deshalb immer vorkommen, dass die verwendete Schlüsselmenge sehr viele Kollisionen verursacht. Man sieht in der Tat leicht ein, dass für jede gegebene Hashfunktion ungünstige Schlüsselmengen &amp;lt;math&amp;gt; U_f \subset U&amp;lt;/math&amp;gt; existieren, bei denen es sehr viele Kollisionen gibt. Im ungünstigsten Fall könnte U&amp;lt;sub&amp;gt;f&amp;lt;/sub&amp;gt; so gewählt sein, dass f(U&amp;lt;sub&amp;gt;f&amp;lt;/sub&amp;gt;) = k = const. gilt. Ein Hacker, der die verwendete Hashfunktion kennt, kann z.B. U&amp;lt;sub&amp;gt;f&amp;lt;/sub&amp;gt; absichtlich so wählen, um eine denial-of-service-Attacke gegen einen hash-basierten Webservice zu starten. Ein anderes anschauliches Beispiel wäre eine Party, zu der nur Leute eingeladen werden, die an einem 8ten im Monat Geburtstag haben. Auf dieser Party ist es viel wahrscheinlicher, Leute zu finden, die am selben (oder gleichen) Tag Geburtstag haben, als wenn man alle einlädt.&lt;br /&gt;
&lt;br /&gt;
D.h. die Wahl einer guten Hashfunktion ist eine Kunst, und man muss (wenn möglich) die Daten analysieren um ein gutes f zu finden.&lt;br /&gt;
&lt;br /&gt;
====Perfektes Hashing====&lt;br /&gt;
&lt;br /&gt;
Kennt man die Untermenge der tatsächlich vorkommenden Schlüssel &amp;lt;math&amp;gt;U_f \subset U&amp;lt;/math&amp;gt; schon im voraus, hat man die Möglichkeit, eine ''perfekte Hashfunktion'' ohne Kollisionen zu entwerfen.&lt;br /&gt;
&lt;br /&gt;
;Beispiel anhand der Monatsnamen &lt;br /&gt;
&lt;br /&gt;
U ist in diesem Fall eine Menge von Strings der Länge 9 (weil der September als längster Monatsname 9 Zeichen hat). Es ergeben sich also &amp;lt;math&amp;gt;60^{9}&amp;lt;/math&amp;gt;&amp;gt;&amp;amp;asymp;10&amp;lt;sup&amp;gt;16&amp;lt;/sup&amp;gt;&amp;amp;asymp;2&amp;lt;sup&amp;gt;54&amp;lt;/sup&amp;gt; mögliche Strings, da mit Groß- und Kleinbuchstaben, Umlauten, ß und Leerzeichen 60 Zeichen im deutschen Alphabet vorhanden sind. Von all diesen Möglichkeiten werden genau 12 benutzt:&lt;br /&gt;
:::&amp;lt;math&amp;gt;U_f&amp;lt;/math&amp;gt; = {&amp;quot;Januar&amp;quot;; &amp;quot;Februar&amp;quot;; ... ; &amp;quot;Dezember&amp;quot;}&lt;br /&gt;
* Benutzt man nun als Hashfunktion die Anfangsbuchstaben der Monatsnamen, benötigt man dafür 6 bit. M ist somit 64. &lt;br /&gt;
:::{&amp;quot;Januar&amp;quot;; &amp;quot;Februar&amp;quot;; ... ; &amp;quot;Dezember&amp;quot;} &amp;amp;rarr; {&amp;quot;J&amp;quot;; &amp;quot;F&amp;quot;; &amp;quot;M&amp;quot;; &amp;quot;A&amp;quot;; &amp;quot;M&amp;quot;; &amp;quot;J&amp;quot;; &amp;quot;J&amp;quot;; &amp;quot;A&amp;quot;; &amp;quot;S&amp;quot;; &amp;quot;O&amp;quot;; &amp;quot;N&amp;quot;; &amp;quot;D&amp;quot;}&lt;br /&gt;
:Dabei enstehen viele Kollisionen (J wird 3x verwendet, M 2x, A 2x), die gewählte ist also keine gute Hashfunktion&lt;br /&gt;
* Benutzt man als Hashfunktion die ersten 3 Buchstaben benötigt man 18 bit, M = &amp;lt;math&amp;gt;2^{18}&amp;lt;/math&amp;gt; &lt;br /&gt;
:::{&amp;quot;Januar&amp;quot;; &amp;quot;Februar&amp;quot;; ... ; &amp;quot;Dezember&amp;quot;} &amp;amp;rarr; {&amp;quot;Jan&amp;quot;, &amp;quot;Feb&amp;quot;, &amp;quot;März&amp;quot;, &amp;quot;Apr&amp;quot;, &amp;quot;Mai&amp;quot;, &amp;quot;Jun&amp;quot;, &amp;quot;Jul&amp;quot;, &amp;quot;Aug&amp;quot;, &amp;quot;Sep&amp;quot;, &amp;quot;Okt&amp;quot;, &amp;quot;Nov&amp;quot;, &amp;quot;Dez&amp;quot;}&lt;br /&gt;
:Nun entstehen keine Kollision mehr. Diese Hashfunktion ist deshalb beim Ausfüllen von Formularen und dergleichen sehr beliebt. Dafür ist M aber recht groß.&lt;br /&gt;
&lt;br /&gt;
Die Aufgabe wird also präzisiert: man sucht für &amp;lt;math&amp;gt;U_f&amp;lt;/math&amp;gt; eine '''minimale, perfekte Hashfunktion''', für die &amp;lt;math&amp;gt;|U_f| = M&amp;lt;/math&amp;gt; gilt. Ein Verfahren hierfür ist Gegenstand von Übungsblatt 9.&lt;br /&gt;
&lt;br /&gt;
====Universelles Hashing====&lt;br /&gt;
&lt;br /&gt;
Hier wählt man für eine gegebene Hashtabelle die Hashfunktion per Zufallszahl aus einer (großen) Menge erlaubter Hashfunktion &amp;amp;rarr; Die Wahrscheinlichkeit, dass die Hashfunktion für die Schlüssel ungünstig ist, wird dadruch minimiert. Die oben erwähnte denial-of-service-Attacke ist jetzt nicht mehr möglich, weil kein Hacker die Hashfunktion im voraus kennen kann. Näheres zum universellen Hashing finden Sie in der [http://en.wikipedia.org/wiki/Universal_hashing Wikpedia].&lt;br /&gt;
&lt;br /&gt;
====Kryptographische Hashfunktionen====&lt;br /&gt;
&lt;br /&gt;
In kryptographischen Anwendungen treten neben dem Hauptziel, die Größe des Universums auf eine überschaubare Zahl von Integer-Werten zu reduzieren, zwei weitere Anforderungen, die für Verschlüsselung bzw. verschlüsselte Kommunikation wichtig sind: erstens will man Kollisionen unbedingt vermeiden (damit zwei verschiedene Dokumente oder Passwörter nicht auf den gleichen Hashwert abgebildet werden), und zweitens darf es nicht möglich sein, aus dem Hashwert die urpsrüngliche Nachricht (also das Dokument oder Passwort) zu rekonstruieren. Man wählt deshalb relative große M (128 bit und mehr) sowie spezielle, für diesen Zweck optimierte Hashfunktionen, wie z.B. [http://de.wikipedia.org/wiki/Message-Digest_Algorithm_5 md5] und [http://de.wikipedia.org/wiki/SHA1 sha1]. Weitere Einzelheiten finden Sie in der [http://en.wikipedia.org/wiki/Cryptographic_hash_function Wikipedia].&lt;br /&gt;
&lt;br /&gt;
====Beliebte Standard-Hashfunktionen====&lt;br /&gt;
&lt;br /&gt;
In der Praxis definiert man Hashfunktionen gewöhnlich zweistufig: Zunächst bildet man den Schlüssel auf einen 32 bit Integerwert ab, M' ist damit 2&amp;lt;sup&amp;gt;32&amp;lt;/sup&amp;gt;. Dieser &amp;quot;rohe&amp;quot; Hashwert wird dann mittels der Modulo-Operation auf die eigentliche Größe M des assoziativen Arrays abgebildet:&lt;br /&gt;
:::&amp;lt;math&amp;gt; f(u \in U) = f'(u \in U)\,\%\,M\,=\,h \in [0, 1, \ldots, M-1] &amp;lt;/math&amp;gt;&lt;br /&gt;
mit&lt;br /&gt;
:::&amp;lt;math&amp;gt; f'(u \in U) = h' \in [0, 1, \ldots, 2^{32}-1] &amp;lt;/math&amp;gt;&lt;br /&gt;
Der große Wert von M' sichert, dass man bei der Wahl von M großen Spielraum hat, so dass die Größe des assoziativen Arrays sehr gut an die Menge der zu speichernden Daten angepaßt werden kann. Die Funktion f'(u) definiert man wie folgt:&lt;br /&gt;
* Falls U = &amp;lt;tt&amp;gt;unsigned int&amp;lt;/tt&amp;gt; (32bit int Datentyp) &amp;amp;rArr; f'(u) = u&lt;br /&gt;
* Falls U = &amp;lt;tt&amp;gt;signed int&amp;lt;/tt&amp;gt; &amp;amp;rArr; Typkonvertierung nach &amp;lt;tt&amp;gt;unsigned int&amp;lt;/tt&amp;gt; &amp;amp;rArr; f'(u) = (unsigned int)u&lt;br /&gt;
* Andere Schlüsseltypen (also insbesondere Strings) interpretiert man als Array of byte &amp;amp;rArr; f'(u) konvertiert Array of Byte nach &amp;lt;tt&amp;gt;unsigned int&amp;lt;/tt&amp;gt;. Beispiele für solche Funktionen:&lt;br /&gt;
:: '''Bernsteinfunktion:'''&lt;br /&gt;
     def bHash(u):     # u: Array of Byte&lt;br /&gt;
         h=0&lt;br /&gt;
         for k in u:&lt;br /&gt;
             h = 33 * h + k &lt;br /&gt;
         return h&lt;br /&gt;
:: '''modifizierte Bernsteinfunktion:'''&lt;br /&gt;
     def mbHash(u):    # u: Array of Byte&lt;br /&gt;
         h=0&lt;br /&gt;
         for k in u:&lt;br /&gt;
             h = (33 * h) ^ k  # ^ ist bitweises Xor&lt;br /&gt;
         return h&lt;br /&gt;
:: '''Shift-Add-Xor-Funktion:'''&lt;br /&gt;
     def saxhash(u):   # u: Array of Byte&lt;br /&gt;
         h=0&lt;br /&gt;
         for k in u:&lt;br /&gt;
             h ^= (h &amp;lt;&amp;lt; 5) + (h &amp;gt;&amp;gt; 2) + k  # &amp;lt;&amp;lt; und &amp;gt;&amp;gt; sind Links- bzw. Rechtsshift der Bits, ^= ist bitweise Xor-Zuweisung&lt;br /&gt;
         return h&lt;br /&gt;
:: '''Fowler/Noll/Vo-Funktion:'''&lt;br /&gt;
     def FNVhash(u):   # u: Array of Byte&lt;br /&gt;
         h = 2166136261&lt;br /&gt;
         for k in u:&lt;br /&gt;
             h = (16777619 * h) ^ k   # ähnlich der modifizierten Bernsteinfunktion, aber mit anderen Konstanten&lt;br /&gt;
         return h&lt;br /&gt;
:: Die verwendeten Konstanten sind experimentell so gewählt worden, dass die Hashfunktionen in typischen Praxisanwendungen relativ wenige Kollisionen verursachen. Der tiefere Grund, warum z.B. 33 in der Bernsteinfunktion eine gute Wahl darstellt, ist unbekannt. Es empfielt sich, in einer gegebenen Anwendung mit mehreren Hashfunktionen zu experimentieren. Weitere solche Funktionen und andere nützliche Informationen findet man auf der Seite [http://www.eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx eternallyconfuzzled.com].&lt;br /&gt;
&lt;br /&gt;
== Hashtabellen ==&lt;br /&gt;
&lt;br /&gt;
Eine Hashtabelle ist eine Datenstruktur, die die Funktionalität des assoziativen Arrays mit Hilfe von Hashing realisiert. Das Grundprinzip besteht darin, dass die Hashtabelle intern ein (dynamisches) Array der Größe &amp;lt;tt&amp;gt;capacity&amp;lt;/tt&amp;gt; verwaltet, so dass die Hashwerte als Indizes in diesem Array verwendet werden können (&amp;lt;tt&amp;gt;capacity&amp;lt;/tt&amp;gt; entspricht der Zahl M aus der mathematischen Definition oben). Eine naive Implementation der Einfügeoperation sieht also so aus &lt;br /&gt;
     def __setitem__(self, key, value):    # naive Implementation, funktioniert so nicht&lt;br /&gt;
         index = self.hash(key) % self.capacity&lt;br /&gt;
         self.array[index] = value&lt;br /&gt;
Diese Implementation ist allerdings zu einfach. Wenn nämlich die Schlüssel aus dem Universum U beliebig gewählt werden dürfen, sind Kollisionen unvermeidlich. Tritt aber eine Kollision auf, werden die Daten eines Schlüssels mit den Daten eines anderen Schlüssels überschrieben. Um Kollisionen geschickt zu behandeln gibt es zwei Ansätze:&lt;br /&gt;
* lineare Verkettung&lt;br /&gt;
* offene Adressierung&lt;br /&gt;
&lt;br /&gt;
=== Hashtabelle mit linearer Verkettung (offenes Hashing/geschlossene Adressierung) ===&lt;br /&gt;
&lt;br /&gt;
Man kann dies als die pessimistische Lösung bezeichnen: Man nimmt an, dass Kollisionen häufig auftreten. Deshalb wird unter jedem&lt;br /&gt;
Hashindex gleich eine Liste angelegt, in der Einträge mit gleichem Hashindex aufgenommen werden können. Die Hashtabelle verwaltet ein Array von Listen, und jedes Arrayfeld kann beliebig viele Elemente speichern: Wird ein Element auf den Index &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt; abgebildet, werden die Daten einfach an die betreffende Liste angehängt. Bei Zugriff auf ein Element wird zunächst die passende Liste gesucht (mit Hilfe des Hashwerts), danach erfolgt in dieser Liste eine sequentielle Suche nach dem richtigen Schlüssel.&lt;br /&gt;
&lt;br /&gt;
Um diese Idee implementieren zu können, benötigen wir zunächst eine Hilfsklasse &amp;lt;tt&amp;gt;HashNode&amp;lt;/tt&amp;gt;, die (Schlüssel, Wert)-Paare speichert und mit Hilfe von &amp;lt;tt&amp;gt;next&amp;lt;/tt&amp;gt; eine verkettete Liste realisiert:&lt;br /&gt;
  class HashNode:&lt;br /&gt;
      def __init__(self,key,data,next):&lt;br /&gt;
          self.key = key&lt;br /&gt;
          self.data = data&lt;br /&gt;
          self.next = next    # Verkettung!&lt;br /&gt;
Die eigentliche Hashtabelle wird in der Klasse ''HashTable'' implementiert:&lt;br /&gt;
  class HashTable:&lt;br /&gt;
      def __init__(self):&lt;br /&gt;
         self.capacity = ... # Geeignete Werte siehe unten&lt;br /&gt;
         self.size = 0       # Anzahl der Werte, die zur Zeit tatsächlich gespeichert sind&lt;br /&gt;
         self.array = [None]*self.capacity&lt;br /&gt;
Wie oben bereits erwähnt, werden die Zugriffsoperatoren ''[ ]'' für eine Datenstruktur in Python durch die Funktionen &amp;lt;tt&amp;gt;__setitem__&amp;lt;/tt&amp;gt; bzw. &amp;lt;tt&amp;gt;__getitem__&amp;lt;/tt&amp;gt; implementiert. &lt;br /&gt;
Die &amp;lt;tt&amp;gt;__setitem__&amp;lt;/tt&amp;gt;-Funktion speichert die gegebenen Daten unter dem Schlüssel &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; in der &amp;lt;tt&amp;gt;HashTable&amp;lt;/tt&amp;gt;-Klasse:&lt;br /&gt;
     def __setitem__(self, key, value):&lt;br /&gt;
         index = hash(key) % self.capacity  # hash(...) ist in Python eine vordefinierte Funktion&lt;br /&gt;
         node  = self.array[index]          # finde die zu 'key' gehörende Liste&lt;br /&gt;
         while node is not None:            # sequentielle Suche nach 'key' in dieser Liste&lt;br /&gt;
             if node.key == key:&lt;br /&gt;
                 # Element 'key' ist schon in der Tabelle&lt;br /&gt;
                 # =&amp;gt; überschreibe die Daten mit dem neuen Wert&lt;br /&gt;
                 node.data = value&lt;br /&gt;
                 return&lt;br /&gt;
             # andernfalls: Kollision des Hashwerts, probiere nächsten 'key' aus&lt;br /&gt;
             node = node.next&lt;br /&gt;
         # kein Element hatte den richtigen Schlüssel.&lt;br /&gt;
         # =&amp;gt; es gibt diesen Schlüssel noch nicht&lt;br /&gt;
         #    füge also ein neues Element in die Hashtabelle ein&lt;br /&gt;
         self.array[index] = HashNode(key, value, self.array[index]) # der alte Anfang der Liste wird zum&lt;br /&gt;
                                                                     # Nachfolger des neu eingefügten ersten Elements&lt;br /&gt;
         self.size += 1&lt;br /&gt;
Die Funktion &amp;lt;tt&amp;gt;__getitem__&amp;lt;/tt&amp;gt; gibt die unter dem Schlüssel &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; abgelegten Daten zurück, oder eine Fehlermeldung, falls dieser Schlüssel nicht existiert:&lt;br /&gt;
     def __getitem__(self, key):&lt;br /&gt;
         index = hash(key) % self.capacity&lt;br /&gt;
         node = self.array[index]     # finde die zu 'key' gehörende Liste&lt;br /&gt;
         while node is not None:      # sequentielle Suche nach 'key' in dieser Liste&lt;br /&gt;
              if node.key == key:     # gefunden!&lt;br /&gt;
                  return node.data    # =&amp;gt; Daten zurückgeben&lt;br /&gt;
              node = node.next        # nächsten Schlüssel probieren&lt;br /&gt;
         raise KeyError(key)          # Schlüssel nicht gefunden =&amp;gt; Fehler&lt;br /&gt;
&lt;br /&gt;
==== Komplexität der linearen Verkettung und Wahl der Kapazität ====&lt;br /&gt;
&lt;br /&gt;
Die Komplexität wird durch zwei Operationen bestimmt: erstens das Auffinden der zu einem Schlüssel gehörenden Liste (die in O(1) erfolgt), zweitens das sequentielle Durchsuchen der Liste, die Zeit in O(L) erfordert, wobei L die mittlere Länge der Listen ist. Die Hashtabelle ist also nur schnell, wenn die Länge der Listen möglichst klein ist. Unter der Annahme des ''uniformen Hashings'', wenn also alle Indizes gleich häufig verwendet werden, ist L gleich dem '''Füllstand''' der Hashtabelle:&lt;br /&gt;
:::&amp;lt;math&amp;gt;\alpha = \frac{N}{M} = \frac{\text{size}}{\text{capacity}}&amp;lt;/math&amp;gt; wobei N die Größe &amp;lt;tt&amp;gt;size&amp;lt;/tt&amp;gt; der Hashtabelle und M die Größe &amp;lt;tt&amp;gt;capacity&amp;lt;/tt&amp;gt; des Arrays ist.&lt;br /&gt;
Wenn die Hashwerte uniform sind, entfallen auf jede Liste im Mittel N/M Einträge (N Einträge, verteilt auf M Listen). Die Gesamtkomplexität berechnet sich nach der Sequenzregel zu&lt;br /&gt;
:::&amp;lt;math&amp;gt;O(1+\alpha)&amp;lt;/math&amp;gt;&lt;br /&gt;
Für eine effiziente Suche muss demnach &amp;lt;math&amp;gt;\alpha \in O(1)&amp;lt;/math&amp;gt; gewählt werden. Dies erreicht man, indem man, wie beim dynamischen Array, &amp;lt;tt&amp;gt;capacity&amp;lt;/tt&amp;gt; immer wieder anpasst, falls &amp;lt;tt&amp;gt;size&amp;lt;/tt&amp;gt; zu groß wird. Üblicherweise verdoppelt man &amp;lt;tt&amp;gt;capacity&amp;lt;/tt&amp;gt;, sobald &amp;lt;tt&amp;gt;size == capacity&amp;lt;/tt&amp;gt; erreicht wird. Analog zum dynamischen Array werden die Daten dann aus dem alten Array (&amp;lt;tt&amp;gt;self.array&amp;lt;/tt&amp;gt;) in ein entsprechend vergrößertes neues Array kopiert.&lt;br /&gt;
&lt;br /&gt;
In der C++ Standardbibliothek (Klasse &amp;lt;tt&amp;gt; [http://www.sgi.com/tech/stl/hash_map.html std::hash_map]&amp;lt;/tt&amp;gt;, siehe auch [http://gcc.gnu.org/viewcvs/*checkout*/trunk/libstdc++-v3/src/hashtable.cc GCC hashtable.cc (Primzahlen)] und [http://gcc.gnu.org/viewcvs/*checkout*/trunk/libstdc++-v3/include/tr1_impl/hashtable_policy.h GCC Hash Implementation]) wird die Hashtabelle häufig so&lt;br /&gt;
implementiert. Dabei wird &amp;lt;tt&amp;gt;capacity&amp;lt;/tt&amp;gt; immer als ''Primzahl'' gewählt, wobei sich aufeinanderfolgende Kapazitäten immer ungefähr verdoppeln:&lt;br /&gt;
  53, 97, 193, 398, 769, ...&lt;br /&gt;
Die Wahl von Primzahlen hat zur Folge, dass &amp;lt;tt&amp;gt;hash(key) % self.capacity&amp;lt;/tt&amp;gt; ''alle'' Bits von h benutzt (Eigenschaft aus der Zahlentheorie). Die Kapizität wird vergrößert, wenn &amp;lt;tt&amp;gt;size == capacity&amp;lt;/tt&amp;gt; erreicht wird, und die ungefähre Verdoppelung sichert, dass die amortisierte Komplexität der Einfügeoperation in O(1) ist (wie beim dynamischen Array).&lt;br /&gt;
&lt;br /&gt;
=== Hashtabelle mit offener Adressierung (geschlossenes Hashing) ===&lt;br /&gt;
[[Image:HASHTB12.svg.png|frame|Prinzip ([http://en.wikipedia.org/wiki/Hash_table Quelle])]]&lt;br /&gt;
&lt;br /&gt;
Dies kann als die optimistische Variante betrachtet werden: man nimmt an, dass Kollisionen nicht so häufig auftreten, um eine komplexe Datenstruktur wie das &amp;quot;Array von Listen&amp;quot; zu rechtfertigen. Stattdessen behandelt man Kollisionen mit einer einfachen '''Idee''': Wenn &amp;lt;tt&amp;gt;array[index]&amp;lt;/tt&amp;gt; durch Kollision bereits vergeben ist, probiere einen&lt;br /&gt;
anderen Index aus (siehe auch [http://de.wikipedia.org/wiki/Hashtabelle#Hashing_mit_offener_Adressierung Wikipedia (de)] und&lt;br /&gt;
[http://en.wikipedia.org/wiki/Open_addressing Wikipedia (en)]). Dabei muss man folgendes beachten:&lt;br /&gt;
&lt;br /&gt;
* Das Array enthält pro Element höchstens ein (key,value)-Paar&lt;br /&gt;
* Das Array muss stets mindestens ''einen'' freien Platz haben (sonst gäbe es beim Ausprobieren anderer Indizes eine Endlosschleife). Es gilt immer &amp;lt;tt&amp;gt;self.size &amp;lt; self.capacity&amp;lt;/tt&amp;gt;. Dies war bei der vorigen Hash-Implementation mit linearer Verkettung nicht notwendig (aber im Sinne schneller Zugriffszeiten trotzdem wünschenswert).&lt;br /&gt;
&lt;br /&gt;
==== Vorgehen bei Kollisionen ====&lt;br /&gt;
&lt;br /&gt;
=====Sequentielles Sondieren=====&lt;br /&gt;
&lt;br /&gt;
Probiere den nächsten Index: &amp;lt;tt&amp;gt;index = (index+1) % capacity&amp;lt;/tt&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Vorteil: einfach&lt;br /&gt;
* Nachteil: Clusterbildung&lt;br /&gt;
&lt;br /&gt;
Clusterbildung heißt, dass sich größere zusammenhängende Bereiche bilden die belegt sind, unterbrochen von Bereichen die komplett frei sind. Beim Versuch des Einfügens eines Elements an einen Platz, der schon belegt ist, muss jetzt das ganze Cluster sequentiell durchlaufen werden, bis ein freier Platz gefunden wird. Damit entspricht die Komplexität der Suche der mittleren Länge der belegten Bereiche, was sich entsprechend in einer langsamen Suche widerspiegelt.&lt;br /&gt;
&lt;br /&gt;
=====Doppeltes Hashing=====&lt;br /&gt;
&lt;br /&gt;
[http://de.wikipedia.org/wiki/Doppel-Hashing Wikipedia (de)] [http://en.wikipedia.org/wiki/Double_hashing Wikipedia (en)]&lt;br /&gt;
&lt;br /&gt;
Bestimme einen neuen Index (bei Kollisionen) durch eine ''2. Hashfunktion''.&lt;br /&gt;
&lt;br /&gt;
Das doppelte Hashing wird typischerweise in der Praxis angewendet und liegt auch der Python Implementierung des Datentyps [http://docs.python.org/tut/node7.html#SECTION007500000000000000000 Dictionary] (Syntax &amp;lt;tt&amp;gt;{'a':1, 'b':2, 'c':3}&amp;lt;/tt&amp;gt; zugrunde.&lt;br /&gt;
&lt;br /&gt;
Eine effiziente Implementierung dieses Datentyps ist für die Performance der Skriptsprache Python extrem wichtig, da z.B. beim Aufruf einer Funktion der auszuführunde Code in einem Dictionary unter dem Schlüssel ''Funktionsname'' nachgeschlagen wird oder die Werte lokaler Variablen innerhalb einer Funktion ebenfalls in einem Dictionary zu finden sind.&lt;br /&gt;
&lt;br /&gt;
Für die Implementierung in Python werden wieder die obigen Klassen &amp;lt;tt&amp;gt;HashNode&amp;lt;/tt&amp;gt; (das Attribut &amp;lt;tt&amp;gt;next&amp;lt;/tt&amp;gt; kann allerdings jetzt entfernt werden) und &amp;lt;tt&amp;gt;HashTable&amp;lt;/tt&amp;gt; benötigt, es folgen die angepassten Implementationen von &amp;lt;tt&amp;gt;__setitem__&amp;lt;/tt&amp;gt; und &amp;lt;tt&amp;gt;__getitem__&amp;lt;/tt&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
  def __setitem__(self, key, value):&lt;br /&gt;
      h = hash(key)&lt;br /&gt;
      index = h % self.capacity&lt;br /&gt;
      while True:&lt;br /&gt;
          if self.array[index] is None or self.array[index].key is None:&lt;br /&gt;
               # das Feld ist frei (1. Abfrage)&lt;br /&gt;
               # oder das Feld ist als frei markiert (2. Abfrage)&lt;br /&gt;
               self.array[index] = HashNode(key, value)&lt;br /&gt;
               self.size +=1&lt;br /&gt;
               return&lt;br /&gt;
          if self.array[index].key == key:&lt;br /&gt;
               # Es gibt diesen Schlüssel schon,&lt;br /&gt;
               # überschreibe die Daten&lt;br /&gt;
               self.array[index].data = value&lt;br /&gt;
               return&lt;br /&gt;
          # Letzter Fall: Kollision =&amp;gt; neuer Index durch 2. Hashfunktion&lt;br /&gt;
          index = (5*index+1+h) % self.capacity&lt;br /&gt;
          h = h &amp;gt;&amp;gt; 5&lt;br /&gt;
(für den &amp;lt;tt&amp;gt;&amp;gt;&amp;gt;&amp;lt;/tt&amp;gt;-Operator, siehe die [http://docs.python.org/ref/shifting.html Python Dokumentation])&lt;br /&gt;
&lt;br /&gt;
Die vorgestellte Implementierung orientiert sich an Pythons interner Dictionary Implementierung, der zugehörige Quelltext (mit ausführlichem Kommentar) findet sich unter [http://svn.python.org/view/*checkout*/python/trunk/Objects/dictobject.c dictobject.c Python Implementation (SVN)]&lt;br /&gt;
&lt;br /&gt;
===== Beispiel für doppeltes Hashing =====&lt;br /&gt;
&lt;br /&gt;
Der Übersichtlichkeit wegen wählen wir M'=2&amp;lt;sup&amp;gt;5&amp;lt;/sup&amp;gt; (statt 2&amp;lt;sup&amp;gt;32&amp;lt;/sup&amp;gt;) und eine Kapazität von M=8.&lt;br /&gt;
&lt;br /&gt;
Roher Hashwert (für das Beispiel willkürlich gewählt):&lt;br /&gt;
  h=25&lt;br /&gt;
Erster Index:&lt;br /&gt;
  i0 = h % capacity = 25 % 8 = 1&lt;br /&gt;
Es finde eine Kollision statt. Es wird ein zweiter Index berechnet:&lt;br /&gt;
  i1 = (5*i0 + 1 + h) % 8 = (5*1 + 1 + 25) % 8 = 31 % 8 = 7&lt;br /&gt;
Der Hashwert wird aktualisiert um die höherwertigen Bits von &amp;lt;tt&amp;gt;h&amp;lt;/tt&amp;gt; ins Spiel zu bringen (hier durch &amp;lt;tt&amp;gt;h &amp;gt;&amp;gt; 2&amp;lt;/tt&amp;gt; anstelle von &amp;lt;tt&amp;gt;h &amp;gt;&amp;gt; 5&amp;lt;/tt&amp;gt; im originalen Pythoncode). Wir stellen &amp;lt;tt&amp;gt;h&amp;lt;/tt&amp;gt; als Binärzahl dar, damit der Rechtsshift besser sichtbar wird:&lt;br /&gt;
  h = h &amp;gt;&amp;gt; 2 &lt;br /&gt;
  ==&amp;gt; h = (11001 &amp;gt;&amp;gt; 2) = 00110 = 6&lt;br /&gt;
Es finde wieder eine Kollision statt, so dass ein dritter Index berechnet werden muss.&lt;br /&gt;
  i2 = (5*i1 + 1 + h) % 8 = (5*7 + 1 + 6) % 8 = 42 % 8 = 2&lt;br /&gt;
Der Hashwert wird wiederum aktualisiert:&lt;br /&gt;
  h = h &amp;gt;&amp;gt; 2&lt;br /&gt;
  ==&amp;gt; h = (00110 &amp;gt;&amp;gt; 2) = 00001 = 1&lt;br /&gt;
Es finde eine Kollision statt, und wir berechnen den vierten Index:&lt;br /&gt;
  i3 = (5*i2 + 1 + h) % 8 = (5*2 + 1 + 1) % 8 = 12 % 8 = 4&lt;br /&gt;
Der Hashwert wird nochmals aktualisiert und erreicht jetzt den Wert 0 (der sich dann nicht mehr ändert):&lt;br /&gt;
  h = h &amp;gt;&amp;gt; 2 &lt;br /&gt;
  ==&amp;gt; h = (00110 &amp;gt;&amp;gt; 2) = 0&lt;br /&gt;
Es finde eine Kollision statt. Da jetzt &amp;lt;tt&amp;gt;h = 0&amp;lt;/tt&amp;gt; gilt, und die Zahlen 5 (Multiplikator) und 8 (capacity) teilerfremd sind, werden ab jetzt systematisch alle Indizes von 0 bis 7 durchprobiert (in der durch die Modulo-Operation bestimmten Reihenfolge):&lt;br /&gt;
  i4  = (5*i3  + 1 + h) % 8 = (5*4 + 1 + 0) % 8 = 21 % 8 = 5&lt;br /&gt;
  i5  = (5*i4  + 1 + h) % 8 = (5*5 + 1 + 0) % 8 = 26 % 8 = 2&lt;br /&gt;
  i6  = (5*i5  + 1 + h) % 8 = (5*2 + 1 + 0) % 8 = 11 &amp;amp; 8 = 3&lt;br /&gt;
  i7  = (5*i6  + 1 + h) % 8 = (5*3 + 1 + 0) % 8 = 16 &amp;amp; 8 = 0&lt;br /&gt;
  i8  = (5*i7  + 1 + h) % 8 = (5*0 + 1 + 0) % 8 =  1 &amp;amp; 8 = 1&lt;br /&gt;
  i9  = (5*i8  + 1 + h) % 8 = (5*1 + 1 + 0) % 8 =  6 &amp;amp; 8 = 6&lt;br /&gt;
  i10 = (5*i9  + 1 + h) % 8 = (5*6 + 1 + 0) % 8 = 31 &amp;amp; 8 = 7&lt;br /&gt;
  i11 = (5*i10 + 1 + h) % 8 = (5*7 + 1 + 0) % 8 = 36 &amp;amp; 8 = 4&lt;br /&gt;
Allen Indizes werden also erreicht, bevor sich die Folge wiederholt. Da man &amp;lt;tt&amp;gt;capacity&amp;lt;/tt&amp;gt; immer so wählt, dass mindestens ein Arrayfeld noch frei ist, wird dadurch immer ein geeigneter Platz für das einzufügende Element gefunden.&lt;br /&gt;
&lt;br /&gt;
==== Komplexität der offenen Adressierung ====&lt;br /&gt;
&lt;br /&gt;
* Annahme: uniformes Hashing, das heißt alle Indizes haben gleiche Wahrscheinlichkeit&lt;br /&gt;
* Füllstand &amp;lt;math&amp;gt;\alpha =\frac{N}{M} = \frac{\text{size}}{\text{capacity}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* '''Erfolglose Suche''' (d.h. es wird entweder ein neues Element eingefügt oder ein &amp;lt;tt&amp;gt;KeyError&amp;lt;/tt&amp;gt; geworfen): Untere Schranke für die Komplexität ist &amp;lt;math&amp;gt;\Omega\left(\frac{1}{1-\alpha}\right)&amp;lt;/math&amp;gt; Schritte (= Anzahl der notwendigen index-Berechnungen).&lt;br /&gt;
* '''Erfolgreiche Suche''' &amp;lt;math&amp;gt;\Omega\left(\frac{1}{\alpha}\ln\left(\frac{1} {1-\alpha}\right)\right)&amp;lt;/math&amp;gt; Schritte.&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;0&amp;quot; cellpadding=&amp;quot;5&amp;quot; align=&amp;quot;center&amp;quot;&lt;br /&gt;
! &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;&lt;br /&gt;
! 0.5&lt;br /&gt;
! 0.9&lt;br /&gt;
|- &lt;br /&gt;
| erfolglos&lt;br /&gt;
| 2.0&lt;br /&gt;
| 10&lt;br /&gt;
|-&lt;br /&gt;
| erfolgreich&lt;br /&gt;
| 1.4&lt;br /&gt;
| 2.6&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==== Wahl der Kapazität ====&lt;br /&gt;
Man sieht an der obigen Tabelle, dass die erfolglose Suche (und damit das Einfügen) sehr langsam wird, wenn der Füllstand hoch ist. In Python wird &amp;lt;tt&amp;gt;capacity&amp;lt;/tt&amp;gt; deshalb so gewählt, dass &amp;lt;math&amp;gt;\alpha \leq 2/3&amp;lt;/math&amp;gt;. Falls &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt; größer werden sollte, verdopple die Kapazität und kopiere das alte array in das neue Array (analog zum dynamischen Array)&lt;br /&gt;
&lt;br /&gt;
In Python werden die Kapazitätsgrößen als Zweierpotenzen gewählt, also 4,8,16,32,...,&lt;br /&gt;
so dass &amp;lt;tt&amp;gt;h % self.capacity&amp;lt;/tt&amp;gt; nur die unteren Bits von &amp;lt;tt&amp;gt;h&amp;lt;/tt&amp;gt; benutzt. Die oberen Bits von &amp;lt;tt&amp;gt;h&amp;lt;/tt&amp;gt; kommen erst ins Spiel, wenn bei der Berechnung der 2. Hashfunktion die Aktualisierung &amp;lt;tt&amp;gt;h = h &amp;gt;&amp;gt; 5&amp;lt;/tt&amp;gt; erfolgt. Dies hat sich bei umfangreichen Experimenten als sehr gut Lösung erwiesen.&lt;br /&gt;
&lt;br /&gt;
== Anwendung von Hashing ==&lt;br /&gt;
&lt;br /&gt;
* Hashtabelle, assoziatives Aray&lt;br /&gt;
* Sortieren in linearer Zeit (Übungsaufgabe 6.2)&lt;br /&gt;
* Suchen von Strings in Texten: Rabin-Karp-Algorithmus&lt;br /&gt;
* ...&lt;br /&gt;
&lt;br /&gt;
=== Rabin Karp Algorithmus ===&lt;br /&gt;
[http://de.wikipedia.org/wiki/Rabin-Karp-Algorithmus Wikipedia (de)] [http://en.wikipedia.org/wiki/Rabin-Karp_string_search_algorithm Wikipedia (en)]&lt;br /&gt;
&lt;br /&gt;
In Textverarbeitungsanwendungen ist eine häufig benutzte Funktion die ''Search &amp;amp; Replace'' Funktionalität. Die Suche sollte in O(len(text)) möglich sein, aber ein naiver Algorithmus braucht O(len(text)*len(searchstring))&lt;br /&gt;
&lt;br /&gt;
==== Naive Implementierung der Textsuche ====&lt;br /&gt;
  def search(text, s):&lt;br /&gt;
      M, N = len(text), len(s)&lt;br /&gt;
      for k in range(M-N):&lt;br /&gt;
          if s==text[k:k+N]:   # O(N), da N Zeichen verglichen werden müssen&lt;br /&gt;
              return k&lt;br /&gt;
      return -1 #nicht gefunden&lt;br /&gt;
&lt;br /&gt;
==== Idee des Rabin Karp Algorithmus ====&lt;br /&gt;
Statt Vergleichen &amp;lt;tt&amp;gt;s==text[k:k+N]&amp;lt;/tt&amp;gt;, die O(N) benötigen da N Vergleiche der Buchstaben durchgeführt werden müssen, Vergleiche die Hashs von Suchstring und dem zu untersuchenden Textabschnitt: &amp;lt;tt&amp;gt;hash(s) == hash(text[k:k+N])&amp;lt;/tt&amp;gt;. Dabei muss natürlich &amp;lt;tt&amp;gt;hash(s)&amp;lt;/tt&amp;gt; nur einmal berechnet werden, wohingegen &amp;lt;tt&amp;gt;hash(text[k:k+N])&amp;lt;/tt&amp;gt; immer wieder neu berechnet werden muss. Damit der Vergleich O(1) sein kann, ist es deswegen erforderlich, eine solche Hashfunktion zu haben, die nicht alle Zeichen (das wäre O(N) ) einlesen muss, sondern die vorhergehende Hashfunktion mit einbezieht.&lt;br /&gt;
&lt;br /&gt;
Eine solche Hashfunktion heißt ''Running Hash'' und funktioniert analog zum ''Sliding Mean''.&lt;br /&gt;
&lt;br /&gt;
Die Running Hash Funktion berechnet in O(1) den hash von &amp;lt;tt&amp;gt;text[k+1:k+1+N]&amp;lt;/tt&amp;gt; ausgehend vom hash für &amp;lt;tt&amp;gt;text[k:k+N]&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Idee: Interpretiere den Text als Ziffern in einer base d Darstellung:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;h_k = \text{text}[k]\cdot d^{N-1} + \text{text}[k+1]\cdot d^{N-2} + \cdots + \text{text}[k+N-1]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Für die Basis 10 (Dezimalsystem) ergibt sich also&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;h_k = \text{text}[k]\cdot {10}^{N-1} + \text{text}[k+1]\cdot {10}^{N-2} + \cdots + \text{text}[k+N-1]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Daraus folgt&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;h_{k+1} = 10 \cdot h_k - \text {text}[k]\cdot {10}^{N} + \text {text}[k+N]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Die Komplexität dieses Updates ist O(1), falls man &amp;lt;math&amp;gt;{10}^{N}&amp;lt;/math&amp;gt; vorberechnet hat.&lt;br /&gt;
&lt;br /&gt;
In der Realität wählt man dann d=32 und benutzt noch an einigen Stellen modulo Operationen, um die Zahlen nicht zu groß werden zu lassen.&lt;br /&gt;
&lt;br /&gt;
==== Implementation ====&lt;br /&gt;
  def searchRabinKarp(text, s):&lt;br /&gt;
      ht, hs, dN = 0, 0, 1&lt;br /&gt;
      M, N = len(text), len(s)&lt;br /&gt;
      d, q = 32, 33554393&lt;br /&gt;
      #q ist eine große Primzahl, aber so,&lt;br /&gt;
      #dass d*q &amp;lt; 2**32 (um Überlauf zu vermeiden)&lt;br /&gt;
      &lt;br /&gt;
      #Initialisierung      &lt;br /&gt;
      for k in range(N):&lt;br /&gt;
          ht = (ht*d + ord(text[k])) % q&lt;br /&gt;
          #ord() gibt die ASCIInummer des übergebenen Zeichens zurück&lt;br /&gt;
          hs = (hs*d + ord( s[k] )) % q&lt;br /&gt;
          dN = (dN*d) % q&lt;br /&gt;
      #Die Variablen sind jetzt wie folgt initialisiert:&lt;br /&gt;
      #ht = hash(text[0:N])&lt;br /&gt;
      #hs = hash(s)&lt;br /&gt;
      #dN = (d**N) % q&lt;br /&gt;
      &lt;br /&gt;
      #Hauptschleife&lt;br /&gt;
      k = 0 &lt;br /&gt;
      while k &amp;lt; M-N:&lt;br /&gt;
          if hs == ht and s==text[k:k+N]:&lt;br /&gt;
              return k # serch string an Position k gefunden&lt;br /&gt;
          if k+N &amp;lt; M:&lt;br /&gt;
              ht = (d*ht - dN * ord(text[k]) + dN*q + ord(text[k+N]) ) % q&lt;br /&gt;
          k +=1&lt;br /&gt;
      return -1  # search string nicht gefunden&lt;br /&gt;
&lt;br /&gt;
[[Iteration versus Rekursion|Nächstes Thema]]&lt;/div&gt;</summary>
		<author><name>Jschleic</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Hashing_und_assoziative_Arrays&amp;diff=4730</id>
		<title>Hashing und assoziative Arrays</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Hashing_und_assoziative_Arrays&amp;diff=4730"/>
				<updated>2010-08-10T14:46:57Z</updated>
		
		<summary type="html">&lt;p&gt;Jschleic: /* Realisierung des assoziativen Arrays als Suchbaum */ typo&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Die Mitschrift gibts auch als [http://hci.iwr.uni-heidelberg.de/alda/images/AlDa.pdf PDF].&lt;br /&gt;
&lt;br /&gt;
== Assoziative Arrays ==&lt;br /&gt;
Assoziative Arrays werden genau wie gewöhnliche  Arrays benutzt, sie unterstützen also den lesenden und schreibenden Zugriff über einen Index i&lt;br /&gt;
    X = a[i]&lt;br /&gt;
    a[i] = x&lt;br /&gt;
Im Unterschied zum gewöhnlichen Array, wo i ein Integer im Bereich &amp;lt;math&amp;gt; i \in 0 \ldots N-1&amp;lt;/math&amp;gt; sein muss, kann der Typ von i jetzt ''beliebig'' sein. Eine typische Anwendung ist ein Wörterbuch&lt;br /&gt;
    x = toEnglish['Baum']   # ergibt 'tree'&lt;br /&gt;
In diesem Fall ist der Typ des Index &amp;lt;tt&amp;gt;string&amp;lt;/tt&amp;gt;, was in der Praxis der häufigste Fall ist, weshalb assoziative Arrays oft als ''Dictionary'' bezeichnet werden (so auch in Python, hier heißt der Typ &amp;lt;tt&amp;gt;dict&amp;lt;/tt&amp;gt;). Im allgemeinen kann aber jeder Typ als Index benutzt werden, für den entweder eine Ordnung oder eine Hashfunktion definiert ist. In erstem Fall realisiert man das assoziative Array mit Hilfe eines Suchbaums, in zweiten Fall mit Hilfe einer Hashtabelle.&lt;br /&gt;
&lt;br /&gt;
==Realisierung des assoziativen Arrays als Suchbaum== &lt;br /&gt;
&lt;br /&gt;
Wenn für den Indextyp des assoziativen Arrays eine Ordnung definiert ist (wenn also &amp;lt;tt&amp;gt;i1 &amp;lt; i2&amp;lt;/tt&amp;gt; oder &amp;lt;tt&amp;gt;cmp(i1, i2)&amp;lt;/tt&amp;gt; unterstützt werden), kann man das Indexierungsproblem auf das Suchproblem zurückführen, indem man die Indizes als Suchschlüssel verwendet. Im einfachsten Fall kann dies mit sequentieller Suche realisiert werden: Man verwendet ein gewöhnliches Array, dessen Einträge (Schlüssel, Daten)-Paare sind. Bei der Frage nach einem Schlüssel wird das betreffende Paar gesucht und die darin gespeicherten Daten zurückgegeben. Dies erfordert aber einen Suchaufwand in O(n). Effizienter geht es mit einem Suchbaum. Die Datenstruktur des Suchbaums muss dafür so erweitert werden, dass zu jedem Schlüssel (=Index) weitere Informationen gespeichert werden können (nämlich der Inhalt des indexierten Feldes). Man erweitert die Node-Klasse um ein Feld &amp;quot;data&amp;quot;:&lt;br /&gt;
    class Node:&lt;br /&gt;
        def __init__(self, key, data = None):&lt;br /&gt;
            self.key = key&lt;br /&gt;
            self.data = data&lt;br /&gt;
            self.left = self.right = None&lt;br /&gt;
Dann kann man eine Klasse &amp;lt;tt&amp;gt;AssociativeArray&amp;lt;/tt&amp;gt; realisieren, die die Indexoperationen intern mit Hilfe der Baumsuche implementiert. In Python wird der Zugriffsoperator ''[ ]'' für eine Datenstruktur (also innerhalb einer Klasse) wie folgt implementiert (vgl. die [http://docs.python.org/ref/sequence-types.html Python Docs zum Thema]):&lt;br /&gt;
        def __setitem__(self, key, value)&lt;br /&gt;
so dass im Programmtext dann folgende Syntax möglich ist: &amp;lt;tt&amp;gt;a[key] = value&amp;lt;/tt&amp;gt; (schreibender Zugriff auf &amp;lt;tt&amp;gt;a[key]&amp;lt;/tt&amp;gt;).&lt;br /&gt;
Analog wird der lesende Zugriff &amp;lt;tt&amp;gt;value = a[key]&amp;lt;/tt&amp;gt; wie folgt umgesetzt:&lt;br /&gt;
        def __getitem__(self, key, value)&lt;br /&gt;
Der Konstruktor der Klasse &amp;lt;tt&amp;gt;AssociativeArray&amp;lt;/tt&amp;gt; initialisiert einen leeren Suchbaum:&lt;br /&gt;
    class AssociativeArray:&lt;br /&gt;
        def __init__(self):&lt;br /&gt;
            self.root = None&lt;br /&gt;
            self.size = 0&lt;br /&gt;
Die Funktion &amp;lt;tt&amp;gt;__setitem__&amp;lt;/tt&amp;gt; schaut nach, ob ein Eintrag mit dem betreffenden Index bereits existiert. Wenn ja, werden seine Daten mit den neuen Daten überschrieben, andernfalls wird ein neuer Eintrag angelegt. Intern werden dazu die bereits bekannten Funktionen &amp;lt;tt&amp;gt;treeSearch&amp;lt;/tt&amp;gt; und &amp;lt;tt&amp;gt;treeInsert&amp;lt;/tt&amp;gt; verwendet (siehe Abschnitt [[Suchen#Suchbäume|Suchbäume]]):&lt;br /&gt;
        def __setitem__(self, index, data):&lt;br /&gt;
             node = treeSearch(self.root, index)&lt;br /&gt;
             if node is None:&lt;br /&gt;
                 self.root = treeInsert(self.root, index)&lt;br /&gt;
                 self.size += 1&lt;br /&gt;
                 node = treeSearch(self.root, index) &lt;br /&gt;
             node.data = data&lt;br /&gt;
(Eine geschicktere Implementation würde natürlich die wiederholten Aufrufe von &amp;lt;tt&amp;gt;treeSearch&amp;lt;/tt&amp;gt; und &amp;lt;tt&amp;gt;treeInsert&amp;lt;/tt&amp;gt; eliminieren. Dies ändert aber nichts an der Komplexität der Funktion.) Die Funktion &amp;lt;tt&amp;gt;__getitem__&amp;lt;/tt&amp;gt; sucht ebenfalls einen Eintrag mit dem gegebenen Index. Wenn er gefunden wird, gibt sie die zugehörigen Daten zurück, andernfalls eine Fehlermeldung:&lt;br /&gt;
        def __getitem__(self, index):&lt;br /&gt;
            node = treeSearch(self.root, index)&lt;br /&gt;
            if node is None:&lt;br /&gt;
                raise KeyError(index)&lt;br /&gt;
            else:&lt;br /&gt;
                return node.data&lt;br /&gt;
Die Indexoperationen haben bei der Realisierung mit Baumsuche eine Komplexität in O(log n).&lt;br /&gt;
&lt;br /&gt;
Ein wichtiges Beispiel für ein assoziatives Array, das auf diese Weise realisiert wurde, ist die C++ Standardklasse &amp;lt;tt&amp;gt;[http://www.sgi.com/tech/stl/Map.html std::map]&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Hashing ==&lt;br /&gt;
&lt;br /&gt;
Nun stellt sich die Frage, ob die Suche in einem assoziativen Array auch schneller, möglicherweise sogar in O(1), geht. Die Antwort lautet: Ja, wenn für die Indexklasse eine Hashfunktion definiert ist.&lt;br /&gt;
&lt;br /&gt;
===Hashfunktionen===&lt;br /&gt;
&lt;br /&gt;
Gegeben sei ein Universum U, dass die Menge aller legalen Schlüssel darstellt. Die Mächtigkeit |U| der Menge U ist im allgemeinen sehr groß. Beispielsweise kann man mit Strings der Länge 9 bis zu 27&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt;&amp;amp;asymp;10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt;&amp;amp;asymp;2&amp;lt;sup&amp;gt;43&amp;lt;/sup&amp;gt; verschiedene Schlüssel generieren, wenn 27 Zeichen erlaubt sind (Kleinbuchstaben und Leerzeichen). Die Grundannahme von Hashing ist jetzt, dass in jeder gegebenen Anwendung nur ein (kleiner) Teil der erlaubten Schlüssel tatsächlich verwendet wird. Man definiert eine Hashfunktion, die jeden Schlüssel auf eine natürliche Zahl im Bereich 0...(M-1) abbildet, wobei M viel kleiner als |U| ist. &lt;br /&gt;
;Definition einer Hashfunktion: &lt;br /&gt;
:::&amp;lt;math&amp;gt; f: U  \rightarrow [0, 1, \ldots, M-1] \subset \mathbb{N} &amp;lt;/math&amp;gt;&lt;br /&gt;
:::&amp;lt;math&amp;gt; f(u \in U) = h \in [0, 1, \ldots, M-1]&amp;lt;/math&amp;gt;&lt;br /&gt;
h wird als ''Hashwert'' von u bezeichnet. Da M &amp;lt; |U|, werden notwendigerweise einige Schlüssel auf dieselbe Zahl abgebildet. Man bezeichnet den Fall &amp;lt;math&amp;gt; f(u_1 \in U) = f(u_2 \in U) &amp;lt;/math&amp;gt; als ''Kollision'' zwischen den Schlüsseln u&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; und u&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Die '''Aufgabe''' besteht jetzt darin, ein Hash-Funktion zu entwerfen, die möglichst wenige Kollisionen hat. Hashfunktionen ähneln damit einem Zufallszahlengenerator, weil jede Zahl &amp;lt;math&amp;gt; h \in 0 \ldots (M-1) &amp;lt;/math&amp;gt; nach Möglichkeit mit gleicher Wahrscheinlichkeit herauskommen soll. Wird dieses Ziel erreicht, spricht man vom ''uniformen Hashing''.&lt;br /&gt;
&lt;br /&gt;
In der Regel ist aber nicht vorher bekannt, welche Schlüssel in einer Anwendung verwendet werden. Es kann deshalb immer vorkommen, dass die verwendete Schlüsselmenge sehr viele Kollisionen verursacht. Man sieht in der Tat leicht ein, dass für jede gegebene Hashfunktion ungünstige Schlüsselmengen &amp;lt;math&amp;gt; U_f \subset U&amp;lt;/math&amp;gt; existieren, bei denen es sehr viele Kollisionen gibt. Im ungünstigsten Fall könnte U&amp;lt;sub&amp;gt;f&amp;lt;/sub&amp;gt; so gewählt sein, dass f(U&amp;lt;sub&amp;gt;f&amp;lt;/sub&amp;gt;) = k = const. gilt. Ein Hacker, der die verwendete Hashfunktion kennt, kann z.B. U&amp;lt;sub&amp;gt;f&amp;lt;/sub&amp;gt; absichtlich so wählen, um eine denial-of-service-Attacke gegen einen hash-basierten Webservice zu starten. Ein anderes anschauliches Beispiel wäre eine Party, zu der nur Leute eingeladen werden, die an einem 8ten im Monat Geburtstag haben. Auf dieser Party ist es viel wahrscheinlicher, Leute zu finden, die am selben (oder gleichen) Tag Geburtstag haben, als wenn man alle einlädt.&lt;br /&gt;
&lt;br /&gt;
D.h. die Wahl einer guten Hashfunktion ist eine Kunst, und man muss (wenn möglich) die Daten analysieren um ein gutes f zu finden.&lt;br /&gt;
&lt;br /&gt;
====Perfektes Hashing====&lt;br /&gt;
&lt;br /&gt;
Kennt man die Untermenge der tatsächlich vorkommenden Schlüssel &amp;lt;math&amp;gt;U_f \subset U&amp;lt;/math&amp;gt; schon im voraus, hat man die Möglichkeit, eine ''perfekte Hashfunktion'' ohne Kollisionen zu entwerfen.&lt;br /&gt;
&lt;br /&gt;
;Beispiel anhand der Monatsnamen &lt;br /&gt;
&lt;br /&gt;
U ist in diesem Fall eine Menge von Strings der Länge 9 (weil der September als längster Monatsname 9 Zeichen hat). Es ergeben sich also &amp;lt;math&amp;gt;60^{9}&amp;lt;/math&amp;gt;&amp;gt;&amp;amp;asymp;10&amp;lt;sup&amp;gt;16&amp;lt;/sup&amp;gt;&amp;amp;asymp;2&amp;lt;sup&amp;gt;54&amp;lt;/sup&amp;gt; mögliche Strings, da mit Groß- und Kleinbuchstaben, Umlauten, ß und Leerzeichen 60 Zeichen im deutschen Alphabet vorhanden sind. Von all diesen Möglichkeiten werden genau 12 benutzt:&lt;br /&gt;
:::&amp;lt;math&amp;gt;U_f&amp;lt;/math&amp;gt; = {&amp;quot;Januar&amp;quot;; &amp;quot;Februar&amp;quot;; ... ; &amp;quot;Dezember&amp;quot;}&lt;br /&gt;
* Benutzt man nun als Hashfunktion die Anfangsbuchstaben der Monatsnamen, benötigt man dafür 6 bit. M ist somit 64. &lt;br /&gt;
:::{&amp;quot;Januar&amp;quot;; &amp;quot;Februar&amp;quot;; ... ; &amp;quot;Dezember&amp;quot;} &amp;amp;rarr; {&amp;quot;J&amp;quot;; &amp;quot;F&amp;quot;; &amp;quot;M&amp;quot;; &amp;quot;A&amp;quot;; &amp;quot;M&amp;quot;; &amp;quot;J&amp;quot;; &amp;quot;J&amp;quot;; &amp;quot;A&amp;quot;; &amp;quot;S&amp;quot;; &amp;quot;O&amp;quot;; &amp;quot;N&amp;quot;; &amp;quot;D&amp;quot;}&lt;br /&gt;
:Dabei enstehen viele Kollisionen (J wird 3x verwendet, M 2x, A 2x), die gewählte ist also keine gute Hashfunktion&lt;br /&gt;
* Benutzt man als Hashfunktion die ersten 3 Buchstaben benötigt man 18 bit, M = &amp;lt;math&amp;gt;2^{18}&amp;lt;/math&amp;gt; &lt;br /&gt;
:::{&amp;quot;Januar&amp;quot;; &amp;quot;Februar&amp;quot;; ... ; &amp;quot;Dezember&amp;quot;} &amp;amp;rarr; {&amp;quot;Jan&amp;quot;, &amp;quot;Feb&amp;quot;, &amp;quot;März&amp;quot;, &amp;quot;Apr&amp;quot;, &amp;quot;Mai&amp;quot;, &amp;quot;Jun&amp;quot;, &amp;quot;Jul&amp;quot;, &amp;quot;Aug&amp;quot;, &amp;quot;Sep&amp;quot;, &amp;quot;Okt&amp;quot;, &amp;quot;Nov&amp;quot;, &amp;quot;Dez&amp;quot;}&lt;br /&gt;
:Nun entstehen keine Kollision mehr. Diese Hashfunktion ist deshalb beim Ausfüllen von Formularen und dergleichen sehr beliebt. Dafür ist M aber recht groß.&lt;br /&gt;
&lt;br /&gt;
Die Aufgabe wird also präzisiert: man sucht für &amp;lt;math&amp;gt;U_f&amp;lt;/math&amp;gt; eine '''minimale, perfekte Hashfunktion''', für die &amp;lt;math&amp;gt;|U_f| = M&amp;lt;/math&amp;gt; gilt. Ein Verfahren hierfür ist Gegenstand von Übungsblatt 9.&lt;br /&gt;
&lt;br /&gt;
====Universelles Hashing====&lt;br /&gt;
&lt;br /&gt;
Hier wählt man für eine gegebene Hashtabelle die Hashfunktion per Zufallszahl aus einer (großen) Menge erlaubter Hashfunktion &amp;amp;rarr; Die Wahrscheinlichkeit, dass die Hashfunktion für die Schlüssel ungünstig ist, wird dadruch minimiert. Die oben erwähnte denial-of-service-Attacke ist jetzt nicht mehr möglich, weil kein Hacker die Hashfunktion im voraus kennen kann. Näheres zum universellen Hashing finden Sie in der [http://en.wikipedia.org/wiki/Universal_hashing Wikpedia].&lt;br /&gt;
&lt;br /&gt;
====Kryptographische Hashfunktionen====&lt;br /&gt;
&lt;br /&gt;
In kryptographischen Anwendungen treten neben dem Hauptziel, die Größe des Universums auf eine überschaubare Zahl von Integer-Werten zu reduzieren, zwei weitere Anforderungen, die für Verschlüsselung bzw. verschlüsselte Kommunikation wichtig sind: erstens will man Kollisionen unbedingt vermeiden (damit zwei verschiedene Dokumente oder Passwörter nicht auf den gleichen Hashwert abgebildet werden), und zweitens darf es nicht möglich sein, aus dem Hashwert die urpsrüngliche Nachricht (also das Dokument oder Passwort) zu rekonstruieren. Man wählt deshalb relative große M (128 bit und mehr) sowie spezielle, für diesen Zweck optimierte Hashfunktionen, wie z.B. [http://de.wikipedia.org/wiki/Message-Digest_Algorithm_5 md5] und [http://de.wikipedia.org/wiki/SHA1 sha1]. Weitere Einzelheiten finden Sie in der [http://en.wikipedia.org/wiki/Cryptographic_hash_function Wikipedia].&lt;br /&gt;
&lt;br /&gt;
====Beliebte Standard-Hashfunktionen====&lt;br /&gt;
&lt;br /&gt;
In der Praxis definiert man Hashfunktionen gewöhnlich zweistufig: Zunächst bildet man den Schlüssel auf einen 32 bit Integerwert ab, M' ist damit 2&amp;lt;sup&amp;gt;32&amp;lt;/sup&amp;gt;. Dieser &amp;quot;rohe&amp;quot; Hashwert wird dann mittels der Modulo-Operation auf die eigentliche Größe M des assoziativen Arrays abgebildet:&lt;br /&gt;
:::&amp;lt;math&amp;gt; f(u \in U) = f'(u \in U)\,\%\,M\,=\,h \in [0, 1, \ldots, M-1] &amp;lt;/math&amp;gt;&lt;br /&gt;
mit&lt;br /&gt;
:::&amp;lt;math&amp;gt; f'(u \in U) = h' \in [0, 1, \ldots, 2^{32}-1] &amp;lt;/math&amp;gt;&lt;br /&gt;
Der große Wert von M' sichert, dass man bei der Wahl von M großen Spielraum hat, so dass die Größe des assoziativen Arrays sehr gut an die Menge der zu speichernden Daten angepaßt werden kann. Die Funktion f'(u) definiert man wie folgt:&lt;br /&gt;
* Falls U = &amp;lt;tt&amp;gt;unsigned int&amp;lt;/tt&amp;gt; (32bit int Datentyp) &amp;amp;rArr; f'(u) = u&lt;br /&gt;
* Falls U = &amp;lt;tt&amp;gt;signed int&amp;lt;/tt&amp;gt; &amp;amp;rArr; Typkonvertierung nach &amp;lt;tt&amp;gt;unsigned int&amp;lt;/tt&amp;gt; &amp;amp;rArr; f'(u) = (unsigned int)u&lt;br /&gt;
* Andere Schlüsseltypen (also insbesondere Strings) interpretiert man als Array of byte &amp;amp;rArr; f'(u) konvertiert Array of Byte nach &amp;lt;tt&amp;gt;unsigned int&amp;lt;/tt&amp;gt;. Beispiele für solche Funktionen:&lt;br /&gt;
:: '''Bernsteinfunktion:'''&lt;br /&gt;
     def bHash(u):     # u: Array of Byte&lt;br /&gt;
         h=0&lt;br /&gt;
         for k in u:&lt;br /&gt;
             h = 33 * h + k &lt;br /&gt;
         return h&lt;br /&gt;
:: '''modifizierte Bernsteinfunktion:'''&lt;br /&gt;
     def mbHash(u):    # u: Array of Byte&lt;br /&gt;
         h=0&lt;br /&gt;
         for k in u:&lt;br /&gt;
             h = (33 * h) ^ k  # ^ ist bitweises Xor&lt;br /&gt;
         return h&lt;br /&gt;
:: '''Shift-Add-Xor-Funktion:'''&lt;br /&gt;
     def saxhash(u):   # u: Array of Byte&lt;br /&gt;
         h=0&lt;br /&gt;
         for k in u:&lt;br /&gt;
             h ^= (h &amp;lt;&amp;lt; 5) + (h &amp;gt;&amp;gt; 2) + k  # &amp;lt;&amp;lt; und &amp;gt;&amp;gt; sind Links- bzw. Rechtsshift der Bits, ^= ist bitweise Xor-Zuweisung&lt;br /&gt;
         return h&lt;br /&gt;
:: '''Fowler/Noll/Vo-Funktion:'''&lt;br /&gt;
     def FNVhash(u):   # u: Array of Byte&lt;br /&gt;
         h = 2166136261&lt;br /&gt;
         for k in u:&lt;br /&gt;
             h = (16777619 * h) ^ k   # ähnlich der modifizierten Bernsteinfunktion, aber mit anderen Konstanten&lt;br /&gt;
         return h&lt;br /&gt;
:: Die verwendeten Konstanten sind experimentell so gewählt worden, dass die Hashfunktionen in typischen Praxisanwendungen relativ wenige Kollisionen verursachen. Der tiefere Grund, warum z.B. 33 in der Bernsteinfunktion eine gute Wahl darstellt, ist unbekannt. Es empfielt sich, in einer gegebenen Anwendung mit mehreren Hashfunktionen zu experimentieren. Weitere solche Funktionen und andere nützliche Informationen findet man auf der Seite [http://www.eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx eternallyconfuzzled.com].&lt;br /&gt;
&lt;br /&gt;
== Hashtabellen ==&lt;br /&gt;
&lt;br /&gt;
Eine Hashtabelle ist eine Datenstruktur, die die Funktionalität des assoziativen Arrays mit Hilfe von Hashing realisiert. Das Grundprinzip besteht darin, dass die Hashtabelle intern ein (dynamisches) Array der Größe &amp;lt;tt&amp;gt;capacity&amp;lt;/tt&amp;gt; verwaltet, so dass die Hashwerte als Indizes in diesem Array verwendet werden können (&amp;lt;tt&amp;gt;capacity&amp;lt;/tt&amp;gt; entspricht der Zahl M aus der mathematischen Definition oben). Eine naive Implementation der Einfügeoperation sieht also so aus &lt;br /&gt;
     def __setitem__(self, key, value):    # naive Implementation, funktioniert so nicht&lt;br /&gt;
         index = self.hash(key) % self.capacity&lt;br /&gt;
         self.array[index] = value&lt;br /&gt;
Diese Implementation ist allerdings zu einfach. Wenn nämlich die Schlüssel aus dem Universum U beliebig gewählt werden dürfen, sind Kollisionen unvermeidlich. Tritt aber eine Kollision auf, werden die Daten eines Schlüssels mit den Daten eines anderen Schlüssels überschrieben. Um Kollisionen geschickt zu behandeln gibt es zwei Ansätze:&lt;br /&gt;
* lineare Verkettung&lt;br /&gt;
* offene Adressierung&lt;br /&gt;
&lt;br /&gt;
=== Hashtabelle mit linearer Verkettung (offenes Hashing/geschlossene Adressierung) ===&lt;br /&gt;
&lt;br /&gt;
Man kann dies als die pessimistische Lösung bezeichnen: Man nimmt an, dass Kollisionen häufig auftreten. Deshalb wird unter jedem&lt;br /&gt;
Hashindex gleich eine Liste angelegt, in der Einträge mit gleichem Hashindex aufgenommen werden können. Die Hashtabelle verwaltet ein Array von Listen, und jedes Arrayfeld kann beliebig viele Elemente speichern: Wird ein Element auf den Index &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt; abgebildet, werden die Daten einfach an die betreffende Liste angehängt. Bei Zugriff auf ein Element wird zunächst die passende Liste gesucht (mit Hilfe des Hashwerts), danach erfolgt in dieser Liste eine sequentielle Suche nach dem richtigen Schlüssel.&lt;br /&gt;
&lt;br /&gt;
Um diese Idee implementieren zu können, benötigen wir zunächst eine Hilfsklasse &amp;lt;tt&amp;gt;HashNode&amp;lt;/tt&amp;gt;, die (Schlüssel, Wert)-Paare speichert und mit Hilfe von &amp;lt;tt&amp;gt;next&amp;lt;/tt&amp;gt; eine verkettete Liste realisiert:&lt;br /&gt;
  class HashNode:&lt;br /&gt;
      def __init__(self,key,data,next):&lt;br /&gt;
          self.key = key&lt;br /&gt;
          self.data = data&lt;br /&gt;
          self.next = next    # Verkettung!&lt;br /&gt;
Die eigentliche Hashtabelle wird in der Klasse ''HashTable'' implementiert:&lt;br /&gt;
  class HashTable:&lt;br /&gt;
      def __init__(self):&lt;br /&gt;
         self.capacity = ... # Geeignete Werte siehe unten&lt;br /&gt;
         self.size = 0       # Anzahl der Werte, die zur Zeit tatsächlich gespeichert sind&lt;br /&gt;
         self.array = [None]*self.capacity&lt;br /&gt;
Wie oben bereits erwähnt, werden die Zugriffsoperatoren ''[ ]'' für eine Datenstruktur in Python durch die Funktionen &amp;lt;tt&amp;gt;__setitem__&amp;lt;/tt&amp;gt; bzw. &amp;lt;tt&amp;gt;__getitem__&amp;lt;/tt&amp;gt; implementiert. &lt;br /&gt;
Die &amp;lt;tt&amp;gt;__setitem__&amp;lt;/tt&amp;gt;-Funktion speichert die gegebenen Daten unter dem Schlüssel &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; in der &amp;lt;tt&amp;gt;HashTable&amp;lt;/tt&amp;gt;-Klasse:&lt;br /&gt;
     def __setitem__(self, key, value):&lt;br /&gt;
         index = hash(key) % self.capacity  # hash(...) ist in Python eine vordefinierte Funktion&lt;br /&gt;
         node  = self.array[index]          # finde die zu 'key' gehörende Liste&lt;br /&gt;
         while node is not None:            # sequentielle Suche nach 'key' in dieser Liste&lt;br /&gt;
             if node.key == key:&lt;br /&gt;
                 # Element 'key' ist schon in der Tabelle&lt;br /&gt;
                 # =&amp;gt; überschreibe die Daten mit dem neuen Wert&lt;br /&gt;
                 node.data = value&lt;br /&gt;
                 return&lt;br /&gt;
             # andernfalls: Kollision des Hashwerts, probiere nächsten 'key' aus&lt;br /&gt;
             node = node.next&lt;br /&gt;
         # kein Element hatte den richtigen Schlüssel.&lt;br /&gt;
         # =&amp;gt; es gibt diesen Schlüssel noch nicht&lt;br /&gt;
         #    füge also ein neues Element in die Hashtabelle ein&lt;br /&gt;
         self.array[index] = HashNode(key, value, self.array[index]) # der alte Anfang der Liste wird zum&lt;br /&gt;
                                                                     # Nachfolger des neu eingefügten ersten Elements&lt;br /&gt;
         self.size += 1&lt;br /&gt;
Die Funktion &amp;lt;tt&amp;gt;__getitem__&amp;lt;/tt&amp;gt; gibt die unter dem Schlüssel &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; abgelegten Daten zurück, oder eine Fehlermeldung, falls dieser Schlüssel nicht existiert:&lt;br /&gt;
     def __getitem__(self, key):&lt;br /&gt;
         index = hash(key) % self.capacity&lt;br /&gt;
         node = self.array[index]     # finde die zu 'key' gehörende Liste&lt;br /&gt;
         while node is not None:      # sequentielle Suche nach 'key' in dieser Liste&lt;br /&gt;
              if node.key == key:     # gefunden!&lt;br /&gt;
                  return node.data    # =&amp;gt; Daten zurückgeben&lt;br /&gt;
              node = node.next        # nächsten Schlüssel probieren&lt;br /&gt;
         raise KeyError(key)          # Schlüssel nicht gefunden =&amp;gt; Fehler&lt;br /&gt;
&lt;br /&gt;
==== Komplexität der linearen Verkettung und Wahl der Kapazität ====&lt;br /&gt;
&lt;br /&gt;
Die Komplexität wird durch zwei Operationen bestimmt: erstens das Auffinden der zu einem Schlüssel gehörenden Liste (die in O(1) erfolgt), zweitens das sequentielle Durchsuchen der Liste, die Zeit in O(L) erfordert, wobei L die mittlere Länge der Listen ist. Die Hashtabelle ist also nur schnell, wenn die Länge der Listen möglichst klein ist. Unter der Annahme des ''uniformen Hashings'', wenn also alle Indizes gleich häufig verwendet werden, ist L gleich dem '''Füllstand''' der Hashtabelle:&lt;br /&gt;
:::&amp;lt;math&amp;gt;\alpha = \frac{N}{M} = \frac{\text{size}}{\text{capacity}}&amp;lt;/math&amp;gt; wobei N die Größe &amp;lt;tt&amp;gt;size&amp;lt;/tt&amp;gt; der Hashtabelle und M die Größe &amp;lt;tt&amp;gt;capacity&amp;lt;/tt&amp;gt; des Arrays ist.&lt;br /&gt;
Wenn die Hashwerte uniform sind, entfallen auf jede Liste im Mittel N/M Einträge (N Einträge, verteilt auf M Listen). Die Gesamtkomplexität berechnet sich nach der Sequenzregel zu&lt;br /&gt;
:::&amp;lt;math&amp;gt;O(1+\alpha)&amp;lt;/math&amp;gt;&lt;br /&gt;
Für eine effiziente Suche muss demnach &amp;lt;math&amp;gt;\alpha \in O(1)&amp;lt;/math&amp;gt; gewählt werden. Dies erreicht man, indem man, wie beim dynamischen Array, &amp;lt;tt&amp;gt;capacity&amp;lt;/tt&amp;gt; immer wieder anpasst, falls &amp;lt;tt&amp;gt;size&amp;lt;/tt&amp;gt; zu groß wird. Üblicherweise verdoppelt man &amp;lt;tt&amp;gt;capacity&amp;lt;/tt&amp;gt;, sobald &amp;lt;tt&amp;gt;size == capacity&amp;lt;/tt&amp;gt; erreicht wird. Analog zum dynamischen Array werden die Daten dann aus dem alten Array (&amp;lt;tt&amp;gt;self.array&amp;lt;/tt&amp;gt;) in ein entsprechend vergrößertes neues Array kopiert.&lt;br /&gt;
&lt;br /&gt;
In der C++ Standardbibliothek (Klasse &amp;lt;tt&amp;gt; [http://www.sgi.com/tech/stl/hash_map.html std::hash_map]&amp;lt;/tt&amp;gt;, siehe auch [http://gcc.gnu.org/viewcvs/*checkout*/trunk/libstdc++-v3/src/hashtable.cc GCC hashtable.cc (Primzahlen)] und [http://gcc.gnu.org/viewcvs/*checkout*/trunk/libstdc++-v3/include/tr1_impl/hashtable_policy.h GCC Hash Implementation]) wird die Hashtabelle häufig so&lt;br /&gt;
implementiert. Dabei wird &amp;lt;tt&amp;gt;capacity&amp;lt;/tt&amp;gt; immer als ''Primzahl'' gewählt, wobei sich aufeinanderfolgende Kapazitäten immer ungefähr verdoppeln:&lt;br /&gt;
  53, 97, 193, 398, 769, ...&lt;br /&gt;
Die Wahl von Preimzahlen hat zur Folge, dass &amp;lt;tt&amp;gt;hash(key) % self.capacity&amp;lt;/tt&amp;gt; ''alle'' Bits von h benutzt (Eigenschaft aus der Zahlentheorie). Die Kapizität wird vergrößert, wenn &amp;lt;tt&amp;gt;size == capacity&amp;lt;/tt&amp;gt; erreicht wird, und die ungefähre Verdoppelung sichert, dass die amortisierte Komplexität der Einfügeoperation in O(1) ist (wie beim dynamischen Array).&lt;br /&gt;
&lt;br /&gt;
=== Hashtabelle mit offener Adressierung (geschlossenes Hashing) ===&lt;br /&gt;
[[Image:HASHTB12.svg.png|frame|Prinzip ([http://en.wikipedia.org/wiki/Hash_table Quelle])]]&lt;br /&gt;
&lt;br /&gt;
Dies kann als die optimistische Variante betrachtet werden: man nimmt an, dass Kollisionen nicht so häufig auftreten, um eine komplexe Datenstruktur wie das &amp;quot;Array von Listen&amp;quot; zu rechtfertigen. Stattdessen behandelt man Kollisionen mit einer einfachen '''Idee''': Wenn &amp;lt;tt&amp;gt;array[index]&amp;lt;/tt&amp;gt; durch Kollision bereits vergeben ist, probiere einen&lt;br /&gt;
anderen Index aus (siehe auch [http://de.wikipedia.org/wiki/Hashtabelle#Hashing_mit_offener_Adressierung Wikipedia (de)] und&lt;br /&gt;
[http://en.wikipedia.org/wiki/Open_addressing Wikipedia (en)]). Dabei muss man folgendes beachten:&lt;br /&gt;
&lt;br /&gt;
* Das Array enthält pro Element höchstens ein (key,value)-Paar&lt;br /&gt;
* Das Array muss stets mindestens ''einen'' freien Platz haben (sonst gäbe es beim Ausprobieren anderer Indizes eine Endlosschleife). Es gilt immer &amp;lt;tt&amp;gt;self.size &amp;lt; self.capacity&amp;lt;/tt&amp;gt;. Dies war bei der vorigen Hash-Implementation mit linearer Verkettung nicht notwendig (aber im Sinne schneller Zugriffszeiten trotzdem wünschenswert).&lt;br /&gt;
&lt;br /&gt;
==== Vorgehen bei Kollisionen ====&lt;br /&gt;
&lt;br /&gt;
=====Sequentielles Sondieren=====&lt;br /&gt;
&lt;br /&gt;
Probiere den nächsten Index: &amp;lt;tt&amp;gt;index = (index+1) % capacity&amp;lt;/tt&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Vorteil: einfach&lt;br /&gt;
* Nachteil: Clusterbildung&lt;br /&gt;
&lt;br /&gt;
Clusterbildung heißt, dass sich größere zusammenhängende Bereiche bilden die belegt sind, unterbrochen von Bereichen die komplett frei sind. Beim Versuch des Einfügens eines Elements an einen Platz, der schon belegt ist, muss jetzt das ganze Cluster sequentiell durchlaufen werden, bis ein freier Platz gefunden wird. Damit entspricht die Komplexität der Suche der mittleren Länge der belegten Bereiche, was sich entsprechend in einer langsamen Suche widerspiegelt.&lt;br /&gt;
&lt;br /&gt;
=====Doppeltes Hashing=====&lt;br /&gt;
&lt;br /&gt;
[http://de.wikipedia.org/wiki/Doppel-Hashing Wikipedia (de)] [http://en.wikipedia.org/wiki/Double_hashing Wikipedia (en)]&lt;br /&gt;
&lt;br /&gt;
Bestimme einen neuen Index (bei Kollisionen) durch eine ''2. Hashfunktion''.&lt;br /&gt;
&lt;br /&gt;
Das doppelte Hashing wird typischerweise in der Praxis angewendet und liegt auch der Python Implementierung des Datentyps [http://docs.python.org/tut/node7.html#SECTION007500000000000000000 Dictionary] (Syntax &amp;lt;tt&amp;gt;{'a':1, 'b':2, 'c':3}&amp;lt;/tt&amp;gt; zugrunde.&lt;br /&gt;
&lt;br /&gt;
Eine effiziente Implementierung dieses Datentyps ist für die Performance der Skriptsprache Python extrem wichtig, da z.B. beim Aufruf einer Funktion der auszuführunde Code in einem Dictionary unter dem Schlüssel ''Funktionsname'' nachgeschlagen wird oder die Werte lokaler Variablen innerhalb einer Funktion ebenfalls in einem Dictionary zu finden sind.&lt;br /&gt;
&lt;br /&gt;
Für die Implementierung in Python werden wieder die obigen Klassen &amp;lt;tt&amp;gt;HashNode&amp;lt;/tt&amp;gt; (das Attribut &amp;lt;tt&amp;gt;next&amp;lt;/tt&amp;gt; kann allerdings jetzt entfernt werden) und &amp;lt;tt&amp;gt;HashTable&amp;lt;/tt&amp;gt; benötigt, es folgen die angepassten Implementationen von &amp;lt;tt&amp;gt;__setitem__&amp;lt;/tt&amp;gt; und &amp;lt;tt&amp;gt;__getitem__&amp;lt;/tt&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
  def __setitem__(self, key, value):&lt;br /&gt;
      h = hash(key)&lt;br /&gt;
      index = h % self.capacity&lt;br /&gt;
      while True:&lt;br /&gt;
          if self.array[index] is None or self.array[index].key is None:&lt;br /&gt;
               # das Feld ist frei (1. Abfrage)&lt;br /&gt;
               # oder das Feld ist als frei markiert (2. Abfrage)&lt;br /&gt;
               self.array[index] = HashNode(key, value)&lt;br /&gt;
               self.size +=1&lt;br /&gt;
               return&lt;br /&gt;
          if self.array[index].key == key:&lt;br /&gt;
               # Es gibt diesen Schlüssel schon,&lt;br /&gt;
               # überschreibe die Daten&lt;br /&gt;
               self.array[index].data = value&lt;br /&gt;
               return&lt;br /&gt;
          # Letzter Fall: Kollision =&amp;gt; neuer Index durch 2. Hashfunktion&lt;br /&gt;
          index = (5*index+1+h) % self.capacity&lt;br /&gt;
          h = h &amp;gt;&amp;gt; 5&lt;br /&gt;
(für den &amp;lt;tt&amp;gt;&amp;gt;&amp;gt;&amp;lt;/tt&amp;gt;-Operator, siehe die [http://docs.python.org/ref/shifting.html Python Dokumentation])&lt;br /&gt;
&lt;br /&gt;
Die vorgestellte Implementierung orientiert sich an Pythons interner Dictionary Implementierung, der zugehörige Quelltext (mit ausführlichem Kommentar) findet sich unter [http://svn.python.org/view/*checkout*/python/trunk/Objects/dictobject.c dictobject.c Python Implementation (SVN)]&lt;br /&gt;
&lt;br /&gt;
===== Beispiel für doppeltes Hashing =====&lt;br /&gt;
&lt;br /&gt;
Der Übersichtlichkeit wegen wählen wir M'=2&amp;lt;sup&amp;gt;5&amp;lt;/sup&amp;gt; (statt 2&amp;lt;sup&amp;gt;32&amp;lt;/sup&amp;gt;) und eine Kapazität von M=8.&lt;br /&gt;
&lt;br /&gt;
Roher Hashwert (für das Beispiel willkürlich gewählt):&lt;br /&gt;
  h=25&lt;br /&gt;
Erster Index:&lt;br /&gt;
  i0 = h % capacity = 25 % 8 = 1&lt;br /&gt;
Es finde eine Kollision statt. Es wird ein zweiter Index berechnet:&lt;br /&gt;
  i1 = (5*i0 + 1 + h) % 8 = (5*1 + 1 + 25) % 8 = 31 % 8 = 7&lt;br /&gt;
Der Hashwert wird aktualisiert um die höherwertigen Bits von &amp;lt;tt&amp;gt;h&amp;lt;/tt&amp;gt; ins Spiel zu bringen (hier durch &amp;lt;tt&amp;gt;h &amp;gt;&amp;gt; 2&amp;lt;/tt&amp;gt; anstelle von &amp;lt;tt&amp;gt;h &amp;gt;&amp;gt; 5&amp;lt;/tt&amp;gt; im originalen Pythoncode). Wir stellen &amp;lt;tt&amp;gt;h&amp;lt;/tt&amp;gt; als Binärzahl dar, damit der Rechtsshift besser sichtbar wird:&lt;br /&gt;
  h = h &amp;gt;&amp;gt; 2 &lt;br /&gt;
  ==&amp;gt; h = (11001 &amp;gt;&amp;gt; 2) = 00110 = 6&lt;br /&gt;
Es finde wieder eine Kollision statt, so dass ein dritter Index berechnet werden muss.&lt;br /&gt;
  i2 = (5*i1 + 1 + h) % 8 = (5*7 + 1 + 6) % 8 = 42 % 8 = 2&lt;br /&gt;
Der Hashwert wird wiederum aktualisiert:&lt;br /&gt;
  h = h &amp;gt;&amp;gt; 2&lt;br /&gt;
  ==&amp;gt; h = (00110 &amp;gt;&amp;gt; 2) = 00001 = 1&lt;br /&gt;
Es finde eine Kollision statt, und wir berechnen den vierten Index:&lt;br /&gt;
  i3 = (5*i2 + 1 + h) % 8 = (5*2 + 1 + 1) % 8 = 12 % 8 = 4&lt;br /&gt;
Der Hashwert wird nochmals aktualisiert und erreicht jetzt den Wert 0 (der sich dann nicht mehr ändert):&lt;br /&gt;
  h = h &amp;gt;&amp;gt; 2 &lt;br /&gt;
  ==&amp;gt; h = (00110 &amp;gt;&amp;gt; 2) = 0&lt;br /&gt;
Es finde eine Kollision statt. Da jetzt &amp;lt;tt&amp;gt;h = 0&amp;lt;/tt&amp;gt; gilt, und die Zahlen 5 (Multiplikator) und 8 (capacity) teilerfremd sind, werden ab jetzt systematisch alle Indizes von 0 bis 7 durchprobiert (in der durch die Modulo-Operation bestimmten Reihenfolge):&lt;br /&gt;
  i4  = (5*i3  + 1 + h) % 8 = (5*4 + 1 + 0) % 8 = 21 % 8 = 5&lt;br /&gt;
  i5  = (5*i4  + 1 + h) % 8 = (5*5 + 1 + 0) % 8 = 26 % 8 = 2&lt;br /&gt;
  i6  = (5*i5  + 1 + h) % 8 = (5*2 + 1 + 0) % 8 = 11 &amp;amp; 8 = 3&lt;br /&gt;
  i7  = (5*i6  + 1 + h) % 8 = (5*3 + 1 + 0) % 8 = 16 &amp;amp; 8 = 0&lt;br /&gt;
  i8  = (5*i7  + 1 + h) % 8 = (5*0 + 1 + 0) % 8 =  1 &amp;amp; 8 = 1&lt;br /&gt;
  i9  = (5*i8  + 1 + h) % 8 = (5*1 + 1 + 0) % 8 =  6 &amp;amp; 8 = 6&lt;br /&gt;
  i10 = (5*i9  + 1 + h) % 8 = (5*6 + 1 + 0) % 8 = 31 &amp;amp; 8 = 7&lt;br /&gt;
  i11 = (5*i10 + 1 + h) % 8 = (5*7 + 1 + 0) % 8 = 36 &amp;amp; 8 = 4&lt;br /&gt;
Allen Indizes werden also erreicht, bevor sich die Folge wiederholt. Da man &amp;lt;tt&amp;gt;capacity&amp;lt;/tt&amp;gt; immer so wählt, dass mindestens ein Arrayfeld noch frei ist, wird dadurch immer ein geeigneter Platz für das einzufügende Element gefunden.&lt;br /&gt;
&lt;br /&gt;
==== Komplexität der offenen Adressierung ====&lt;br /&gt;
&lt;br /&gt;
* Annahme: uniformes Hashing, das heißt alle Indizes haben gleiche Wahrscheinlichkeit&lt;br /&gt;
* Füllstand &amp;lt;math&amp;gt;\alpha =\frac{N}{M} = \frac{\text{size}}{\text{capacity}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* '''Erfolglose Suche''' (d.h. es wird entweder ein neues Element eingefügt oder ein &amp;lt;tt&amp;gt;KeyError&amp;lt;/tt&amp;gt; geworfen): Untere Schranke für die Komplexität ist &amp;lt;math&amp;gt;\Omega\left(\frac{1}{1-\alpha}\right)&amp;lt;/math&amp;gt; Schritte (= Anzahl der notwendigen index-Berechnungen).&lt;br /&gt;
* '''Erfolgreiche Suche''' &amp;lt;math&amp;gt;\Omega\left(\frac{1}{\alpha}\ln\left(\frac{1} {1-\alpha}\right)\right)&amp;lt;/math&amp;gt; Schritte.&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;0&amp;quot; cellpadding=&amp;quot;5&amp;quot; align=&amp;quot;center&amp;quot;&lt;br /&gt;
! &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;&lt;br /&gt;
! 0.5&lt;br /&gt;
! 0.9&lt;br /&gt;
|- &lt;br /&gt;
| erfolglos&lt;br /&gt;
| 2.0&lt;br /&gt;
| 10&lt;br /&gt;
|-&lt;br /&gt;
| erfolgreich&lt;br /&gt;
| 1.4&lt;br /&gt;
| 2.6&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==== Wahl der Kapazität ====&lt;br /&gt;
Man sieht an der obigen Tabelle, dass die erfolglose Suche (und damit das Einfügen) sehr langsam wird, wenn der Füllstand hoch ist. In Python wird &amp;lt;tt&amp;gt;capacity&amp;lt;/tt&amp;gt; deshalb so gewählt, dass &amp;lt;math&amp;gt;\alpha \leq 2/3&amp;lt;/math&amp;gt;. Falls &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt; größer werden sollte, verdopple die Kapazität und kopiere das alte array in das neue Array (analog zum dynamischen Array)&lt;br /&gt;
&lt;br /&gt;
In Python werden die Kapazitätsgrößen als Zweierpotenzen gewählt, also 4,8,16,32,...,&lt;br /&gt;
so dass &amp;lt;tt&amp;gt;h % self.capacity&amp;lt;/tt&amp;gt; nur die unteren Bits von &amp;lt;tt&amp;gt;h&amp;lt;/tt&amp;gt; benutzt. Die oberen Bits von &amp;lt;tt&amp;gt;h&amp;lt;/tt&amp;gt; kommen erst ins Spiel, wenn bei der Berechnung der 2. Hashfunktion die Aktualisierung &amp;lt;tt&amp;gt;h = h &amp;gt;&amp;gt; 5&amp;lt;/tt&amp;gt; erfolgt. Dies hat sich bei umfangreichen Experimenten als sehr gut Lösung erwiesen.&lt;br /&gt;
&lt;br /&gt;
== Anwendung von Hashing ==&lt;br /&gt;
&lt;br /&gt;
* Hashtabelle, assoziatives Aray&lt;br /&gt;
* Sortieren in linearer Zeit (Übungsaufgabe 6.2)&lt;br /&gt;
* Suchen von Strings in Texten: Rabin-Karp-Algorithmus&lt;br /&gt;
* ...&lt;br /&gt;
&lt;br /&gt;
=== Rabin Karp Algorithmus ===&lt;br /&gt;
[http://de.wikipedia.org/wiki/Rabin-Karp-Algorithmus Wikipedia (de)] [http://en.wikipedia.org/wiki/Rabin-Karp_string_search_algorithm Wikipedia (en)]&lt;br /&gt;
&lt;br /&gt;
In Textverarbeitungsanwendungen ist eine häufig benutzte Funktion die ''Search &amp;amp; Replace'' Funktionalität. Die Suche sollte in O(len(text)) möglich sein, aber ein naiver Algorithmus braucht O(len(text)*len(searchstring))&lt;br /&gt;
&lt;br /&gt;
==== Naive Implementierung der Textsuche ====&lt;br /&gt;
  def search(text, s):&lt;br /&gt;
      M, N = len(text), len(s)&lt;br /&gt;
      for k in range(M-N):&lt;br /&gt;
          if s==text[k:k+N]:   # O(N), da N Zeichen verglichen werden müssen&lt;br /&gt;
              return k&lt;br /&gt;
      return -1 #nicht gefunden&lt;br /&gt;
&lt;br /&gt;
==== Idee des Rabin Karp Algorithmus ====&lt;br /&gt;
Statt Vergleichen &amp;lt;tt&amp;gt;s==text[k:k+N]&amp;lt;/tt&amp;gt;, die O(N) benötigen da N Vergleiche der Buchstaben durchgeführt werden müssen, Vergleiche die Hashs von Suchstring und dem zu untersuchenden Textabschnitt: &amp;lt;tt&amp;gt;hash(s) == hash(text[k:k+N])&amp;lt;/tt&amp;gt;. Dabei muss natürlich &amp;lt;tt&amp;gt;hash(s)&amp;lt;/tt&amp;gt; nur einmal berechnet werden, wohingegen &amp;lt;tt&amp;gt;hash(text[k:k+N])&amp;lt;/tt&amp;gt; immer wieder neu berechnet werden muss. Damit der Vergleich O(1) sein kann, ist es deswegen erforderlich, eine solche Hashfunktion zu haben, die nicht alle Zeichen (das wäre O(N) ) einlesen muss, sondern die vorhergehende Hashfunktion mit einbezieht.&lt;br /&gt;
&lt;br /&gt;
Eine solche Hashfunktion heißt ''Running Hash'' und funktioniert analog zum ''Sliding Mean''.&lt;br /&gt;
&lt;br /&gt;
Die Running Hash Funktion berechnet in O(1) den hash von &amp;lt;tt&amp;gt;text[k+1:k+1+N]&amp;lt;/tt&amp;gt; ausgehend vom hash für &amp;lt;tt&amp;gt;text[k:k+N]&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Idee: Interpretiere den Text als Ziffern in einer base d Darstellung:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;h_k = \text{text}[k]\cdot d^{N-1} + \text{text}[k+1]\cdot d^{N-2} + \cdots + \text{text}[k+N-1]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Für die Basis 10 (Dezimalsystem) ergibt sich also&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;h_k = \text{text}[k]\cdot {10}^{N-1} + \text{text}[k+1]\cdot {10}^{N-2} + \cdots + \text{text}[k+N-1]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Daraus folgt&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;h_{k+1} = 10 \cdot h_k - \text {text}[k]\cdot {10}^{N} + \text {text}[k+N]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Die Komplexität dieses Updates ist O(1), falls man &amp;lt;math&amp;gt;{10}^{N}&amp;lt;/math&amp;gt; vorberechnet hat.&lt;br /&gt;
&lt;br /&gt;
In der Realität wählt man dann d=32 und benutzt noch an einigen Stellen modulo Operationen, um die Zahlen nicht zu groß werden zu lassen.&lt;br /&gt;
&lt;br /&gt;
==== Implementation ====&lt;br /&gt;
  def searchRabinKarp(text, s):&lt;br /&gt;
      ht, hs, dN = 0, 0, 1&lt;br /&gt;
      M, N = len(text), len(s)&lt;br /&gt;
      d, q = 32, 33554393&lt;br /&gt;
      #q ist eine große Primzahl, aber so,&lt;br /&gt;
      #dass d*q &amp;lt; 2**32 (um Überlauf zu vermeiden)&lt;br /&gt;
      &lt;br /&gt;
      #Initialisierung      &lt;br /&gt;
      for k in range(N):&lt;br /&gt;
          ht = (ht*d + ord(text[k])) % q&lt;br /&gt;
          #ord() gibt die ASCIInummer des übergebenen Zeichens zurück&lt;br /&gt;
          hs = (hs*d + ord( s[k] )) % q&lt;br /&gt;
          dN = (dN*d) % q&lt;br /&gt;
      #Die Variablen sind jetzt wie folgt initialisiert:&lt;br /&gt;
      #ht = hash(text[0:N])&lt;br /&gt;
      #hs = hash(s)&lt;br /&gt;
      #dN = (d**N) % q&lt;br /&gt;
      &lt;br /&gt;
      #Hauptschleife&lt;br /&gt;
      k = 0 &lt;br /&gt;
      while k &amp;lt; M-N:&lt;br /&gt;
          if hs == ht and s==text[k:k+N]:&lt;br /&gt;
              return k # serch string an Position k gefunden&lt;br /&gt;
          if k+N &amp;lt; M:&lt;br /&gt;
              ht = (d*ht - dN * ord(text[k]) + dN*q + ord(text[k+N]) ) % q&lt;br /&gt;
          k +=1&lt;br /&gt;
      return -1  # search string nicht gefunden&lt;br /&gt;
&lt;br /&gt;
[[Iteration versus Rekursion|Nächstes Thema]]&lt;/div&gt;</summary>
		<author><name>Jschleic</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Effizienz&amp;diff=4729</id>
		<title>Effizienz</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Effizienz&amp;diff=4729"/>
				<updated>2010-08-10T12:23:07Z</updated>
		
		<summary type="html">&lt;p&gt;Jschleic: /* Optimierung der Laufzeit */ typo&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Bei der Diskussion von Effizienz müssen wir zwischen der Laufzeit eines Algorithmus auf einem bestimmten System und seiner prinzipiellen Leistungsfähigkeit (Algorithmenkomplexität) unterscheiden. Der Benutzer ist natürlich vor allem an der Laufzeit interessiert, denn diese bestimmt letztendlich seine Arbeitsproduktivität. Ein Softwaredesigner hingegen muss eine Implementation wählen, die auf verschiedenen Systemen und in verschiedenen Anwendungen schnell ist. Für ihn sind daher auch Aussagen zur Algorithmenkomplexität sehr wichtig, um den am besten geeigneten Algorithmus auszuwählen.&lt;br /&gt;
&lt;br /&gt;
== Laufzeit ==&lt;br /&gt;
&lt;br /&gt;
Aus Anwendersicht ist ein Algorithmus effizient, wenn er die in der Spezifikation verlangten Laufzeitgrenzen einhält. Ein Algorithmus muss also nicht immer so schnell wie möglich sein, sondern so schnell wie nötig. Dies führt in verschiedenen Anwendungen zu ganz unterschiedliche Laufzeitanforderungen:&lt;br /&gt;
&lt;br /&gt;
* Berechnen des nächsten Steuerkommandos für eine Maschine: ca. 1/1000s&lt;br /&gt;
* Berechnen des nächsten Bildes für eine Videopräsentation (z.B. Dekompression von MPEG-kodierten Bildern): ca. 1/25s&lt;br /&gt;
: Geringere Bildraten führen zu ruckeligen Filmen.&lt;br /&gt;
* Sichtbare Antwort auf ein interaktives Kommando (z.B. Mausklick): ca. 1/2s&lt;br /&gt;
: Wird diese Antwortzeit überschritten, vermuten viele Benutzer, dass der Mausklick nicht funktioniert hat, und klicken nochmals, mit eventuell fatalen Folgen. Wenn ein Algorithmus notwendigerweise länger dauert als 1/2s, sollte ein Fortschrittsbalken angezeigt werden.&lt;br /&gt;
* Wettervorhersage: muss spätestens am Vorabend des vorhergesagten Tages beendet sein&lt;br /&gt;
&lt;br /&gt;
===Laufzeitvergleich===&lt;br /&gt;
&lt;br /&gt;
Da die Laufzeit für den Benutzer ein so wichtiges Kriterium ist, werden häufig Laufzeitvergleiche durchgeführt. Deren Ergebnisse hängen allerdings von vielen Faktoren ab, die möglicherweise nicht kontrollierbar sind:&lt;br /&gt;
* Geschwindigkeit und Anzahl der Prozessoren&lt;br /&gt;
* Auslastung des Systems&lt;br /&gt;
* Größe des Hauptspeichers  und Cache, Geschwindigkeit des  Datenbus&lt;br /&gt;
* Qualität des Compilers/Optimierers (ist der Compiler für die spezielle Prozessor-Architektur optimiert?)&lt;br /&gt;
* Geschick des Programmierers&lt;br /&gt;
* Daten (Beispiel Quicksort: Best case und worst case [vorsortierter Input] stark unterschiedlich)&lt;br /&gt;
All diese Faktoren sind untereinander abhängig. Laufzeitvergleiche sind daher mit Vorsicht zu interpretieren.&lt;br /&gt;
Generell sollten bei Vergleichen möglichst wenige Parameter verändert werden, z.B.&lt;br /&gt;
* gleiches Programm (gleiche Kompilierung), gleiche Daten, andere Prozessoren&lt;br /&gt;
oder&lt;br /&gt;
* gleiche CPU, Daten, andere Programme (Vergleich von Algorithmen)&lt;br /&gt;
Zur Verbesserung der Vergleichbarkeit gibt es standardisierte [http://en.wikipedia.org/wiki/Benchmark_(computing) Benchmarks], die bestimmte Aspekte eines Systems unter möglichst realitätsnahen Bedingungen testen. Generell gilt aber: Durch Laufzeitmessung ist schwer festzustellen, ob ein Algorithmus ''prinzipiell'' besser ist als ein anderer. Dafür ist die Analyse der [[Effizienz#Algorithmen-Komplexität|Algorithmenkomplexität]] notwendig.&lt;br /&gt;
&lt;br /&gt;
===Optimierung der Laufzeit===&lt;br /&gt;
&lt;br /&gt;
Wenn sich herausstellt, dass ein bereits implementierter Algorithmus zu langsam läuft, geht man wie folgt vor:&lt;br /&gt;
&lt;br /&gt;
# Man verwendet einen [http://en.wikipedia.org/wiki/Performance_analysis Profiler], um zunächst den Flaschenhals zu bestimmen. Ein Profiler ist ein Hilfsprogramm, das während der Ausführung eines Programms misst, wieviel Zeit in jeder Funktion und Unterfunktion verbraucht wird. Dadurch kann man herausfinden, welcher Teil des Algorithmus überhaupt Probleme bereitet. Donald Knuth gibt z.B. als Erfahrungswert an, dass Programme während des größten Teils ihrer Laufzeit nur 3% des Quellcodes (natürlich mehrmals wiederholt) ausführen [http://pplab.snu.ac.kr/courses/adv_pl05/papers/p261-knuth.pdf]. Es ist sehr wichtig, diese 3% experimentell zu bestimmen, weil die Erfahrung zeigt, dass man beim Erraten der kritischen Programmteile oft falsch liegt. Man spricht dann von &amp;quot;[http://en.wikipedia.org/wiki/Optimization_%28computer_science%29#When_to_optimize premature optimization]&amp;quot;, also von voreiliger Optimierung ohne experimentelle Untersuchung der wirklichen Laufzeiten, was laut Knuth &amp;quot;the root of all evil&amp;quot; ist. Der Python-Profiler wird in [http://docs.python.org/lib/profile.html Kapitel 25] der Python-Dokumentation beschrieben.&lt;br /&gt;
# Man kann dann versuchen, die kritischen Programmteile zu optimieren.&lt;br /&gt;
# Falls der Laufzeitgewinn durch Optimierung zu gering ist, muss man einen prinzipiell schnelleren Algorithmus verwenden, falls es einen gibt.&lt;br /&gt;
&lt;br /&gt;
Einige wichtige Techniken der Programmoptimierung sollen hier erwähnt werden. Wenn man einen optimierenden Compiler verwendet, werden einige Optimierungen automatisch ausgeführt [http://en.wikipedia.org/wiki/Compiler_optimization]. In Python trifft dies jedoch nicht zu. Um den Sinn einiger Optimierungen zu verstehen, benötigt man Grundkenntnisse der Computerarchitektur.&lt;br /&gt;
&lt;br /&gt;
;Elimination von redundantem Code: Es ist offensichtlich überflüssig, dasselbe Ergebnis mehrmals zu berechnen, wenn es auch zwischengespeichert werden könnte. Diese Optimierung wird von vielen automatischen Optimierern unterstützt und kommt im wesentlichen in zwei Ausprägungen vor:&lt;br /&gt;
:; common subexpression elimination: In mathematischen Ausdrücken wird ein Teilergebnis häufig mehrmals benötigt. Man betrachte z.B. die Lösung der quadratischen Gleichung &amp;lt;math&amp;gt;x^2+p\,x+q = 0&amp;lt;/math&amp;gt;:&lt;br /&gt;
        x1 = - p / 2.0 + sqrt(p*p/4.0 - q)&lt;br /&gt;
        x2 = - p / 2.0 - sqrt(p*p/4.0 - q)&lt;br /&gt;
::Die mehrmalige Berechnung von Teilausdrücken wird vermieden, wenn man stattdessen schreibt:&lt;br /&gt;
        p2 = - p / 2.0&lt;br /&gt;
        r  = sqrt(p2*p2 - q)&lt;br /&gt;
        x1 = p2 + r&lt;br /&gt;
        x2 = p2 - r&lt;br /&gt;
:; loop invariant elimination: Wenn ein Teilausdruck sich in einer Schleife nicht ändert, muss man ihn nicht bei jedem Schleifendurchlauf neu berechnen, sondern kann dies einmal vor Beginn der Schleife tun. Ein typisches Beispiel hierfür ist die Adressierung von Matrizen, die als 1-dimensionales Array gespeichert sind. Angenommen, wir speichern eine NxN Matrix &amp;lt;tt&amp;gt;m&amp;lt;/tt&amp;gt; in einem Array &amp;lt;tt&amp;gt;a&amp;lt;/tt&amp;gt; der Größe N&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, so dass das Matrixelement &amp;lt;tt&amp;gt;m&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt;&amp;lt;/tt&amp;gt; durch &amp;lt;tt&amp;gt;a[i + j*N]&amp;lt;/tt&amp;gt; indexiert wird. Wir betrachten die Aufgabe, eine Einheitsmatrix zu initialisieren. Ein nicht optimierter Algorithmus dafür lautet:&lt;br /&gt;
       for j in range(N):&lt;br /&gt;
           for i in range(N):&lt;br /&gt;
               if i == j:&lt;br /&gt;
                    a[i + j*N] = 1.0&lt;br /&gt;
               else:&lt;br /&gt;
                    a[i + j*N] = 0.0&lt;br /&gt;
::Der Ausdruck &amp;lt;tt&amp;gt;j*N&amp;lt;/tt&amp;gt; wird hier in jedem Schleifendurchlauf erneut berechnet, obwohl sich &amp;lt;tt&amp;gt;j&amp;lt;/tt&amp;gt; in der inneren Schleife gar nicht verändert. Man kann deshalb optimieren zu:&lt;br /&gt;
       for j in range(N):&lt;br /&gt;
           jN = j*N&lt;br /&gt;
           for i in range(N):&lt;br /&gt;
               if i == j:&lt;br /&gt;
                    a[i + jN] = 1.0&lt;br /&gt;
               else:&lt;br /&gt;
                    a[i + jN] = 0.0&lt;br /&gt;
;Vereinfachung der inneren Schleife: Generell sollte man sich bei der Optimierung auf die innere Schleife eines Algorithmus konzentrieren, weil dieser Code am häufigsten ausgeführt wird. Insbesondere sollte man die Anzahl der Befehle in der inneren Schleife so gering wie möglich halten und teure Befehle vermeiden. Früher waren vor allem Floating-Point Befehle teuer, die man oft durch die schnellere Integer-Arithmetik ersetzt hat, falls dies algorithmisch möglich war (diesen Rat findet man noch oft in der Literatur). Heute hat sich die Hardware so verbessert, dass im Allgemeinen nur noch die Floating-Point Division deutlich langsamer ist als die anderen Operatoren. Im obigen Beispiel der quadratischen Gleichung ist es daher sinnvoll, den Ausdruck &lt;br /&gt;
        p2 = -p / 2.0&lt;br /&gt;
:durch&lt;br /&gt;
        p2 = -0.5 * p&lt;br /&gt;
:zu ersetzen. Dadurch ersetzt man eine Division durch eine Multiplikation und spart außerdem das Negieren von &amp;lt;tt&amp;gt;p&amp;lt;/tt&amp;gt;, da der Compiler direkt mit &amp;lt;tt&amp;gt;-0.5&amp;lt;/tt&amp;gt; multipliziert.&lt;br /&gt;
;Ausnutzung der Prozessor-Pipeline: Moderne Prozessoren führen mehrere Befehle parallel aus. Dies ist möglich, weil jeder Befehl in mehrere Teilschritte zerlegt werden kann. Eine generische Unterteilung in vier Teilschritte ist z.B.:&lt;br /&gt;
:# Dekodieren des nächsten Befehls&lt;br /&gt;
:# Beschaffen der Daten, die der Befehl verwendet (aus Prozessorregistern, dem Cache, oder dem Hauptspeicher)&lt;br /&gt;
:# Ausführen des Befehls&lt;br /&gt;
:# Schreiben der Ergebnisse&lt;br /&gt;
:Man bezeichnet dies als die &amp;quot;[http://en.wikipedia.org/wiki/Instruction_pipeline instruction pipeline]&amp;quot; des Prozessors (heutige Prozessoren verwenden wesentlich feinere Unterteilungen). Prozessoren werden nun so gebaut, dass mehrere Befehle parallel, auf verschiedenen Ausführungsstufen ausgeführt werden. Wenn Befehl 1 also beim Schreiben der Ergebnisse angelangt ist, kann Befehl 2 die Hardware zum Ausführen des Befehls benutzen, während Befehl 3 seine Daten holt, und Befehl 4 soeben dekodiert wird. Unter bestimmten Bedingungen funktioniert diese Parallelverarbeitung jedoch nicht. Dies gibt Anlass zu Optimierungen:&lt;br /&gt;
:;Vermeiden unnötiger Typkonvertierungen: Der Prozessor verarbeitet Interger- und Floating-Point-Befehle in verschiedenen Pipelines, weil die Hardwareanforderungen sehr verschieden sind. Wird jetzt ein Ergebnis von Integer nach Floating-Point umgewandelt oder umgekehrt, muss die jeweils andere Pipeline warten, bis die erste Pipeline ihre Berechnung beendet. Es kann dann besser sein, Berechnungen in Floating-Point zu Ende zu führen, auch wenn sie semantisch eigentlich Integer-Berechnungen sind.&lt;br /&gt;
:;Reduzierung der Anzahl von Verzweigungen: Wenn der Code verzweigt (z.B. durch eine &amp;lt;tt&amp;gt;if&amp;lt;/tt&amp;gt;- oder  &amp;lt;tt&amp;gt;while&amp;lt;/tt&amp;gt;-Anweisung), ist nicht klar, welcher Befehl nach der Verzweigung ausgeführt werden soll, bevor Stufe 3 der Pipeline die Verzweigungsbedingung ausgewertet hat. Bis dahin wären die ersten beiden Stufen der Pipeline unbenutzt. Moderne Prozessoren benutzen zwar ausgefeilte Heuristiken, um das Ergebnis der Bedingung vorherzusagen, und führen den hoffentlich richtigen Zweig des Codes spekulativ aus, aber dies funktioniert nicht immer. Man sollte deshalb generell die Anzahl der Verzweigungen minimieren. Als Nebeneffekt führt dies meist auch zu besser lesbarem, verständlicherem Code. Im Matrixbeispiel kann man&lt;br /&gt;
       for j in range(N):&lt;br /&gt;
           jN = j*N&lt;br /&gt;
           for i in range(N):&lt;br /&gt;
               if i == j:&lt;br /&gt;
                    a[i + jN] = 1.0&lt;br /&gt;
               else:&lt;br /&gt;
                    a[i + jN] = 0.0&lt;br /&gt;
::durch&lt;br /&gt;
       for j in range(N):&lt;br /&gt;
           jN = j*N&lt;br /&gt;
           for i in range(N):&lt;br /&gt;
               a[i + jN] = 0.0&lt;br /&gt;
           a[j + jN] = 1.0&lt;br /&gt;
ersetzen. Die Diagonalelemente &amp;lt;tt&amp;gt;a[j + jN]&amp;lt;/tt&amp;gt; werden jetzt zwar zweimal initialisiert (in der Schleife auf Null, dann auf Eins), aber durch Elimination der &amp;lt;tt&amp;gt;if&amp;lt;/tt&amp;gt;-Abfrage wird dies wahrscheinlich mehr als ausgeglichen, zumal dadurch die innere Schleife wesentlich vereinfacht wurde.&lt;br /&gt;
;Ausnutzen des Prozessor-Cache: Zugriffe auf den Hauptspeicher sind sehr langsam. Deshalb werden stets ganze Speicherseiten auf einmal in den [http://en.wikipedia.org/wiki/Cache Cache] des Prozessors geladen. Wenn unmittelbar nacheinander benutzte Daten auch im Speicher nahe beieinander liegen (sogenannte &amp;quot;[http://en.wikipedia.org/wiki/Locality_of_reference locality of reference]&amp;quot;), ist die Wahrscheinlichkeit groß, dass die als nächstes benötigten Daten bereits im Cache sind und damit schnell gelesen werden können. Bei vielen Algorithmen kann man die Implementation so umordnen, dass die locality of reference verbessert wird, was zu einer drastischen Beschleunigung führt. Im Matrix-Beispiel ist z.B. die Reihenfolge der Schleifen wichtig. Für konstanten Index &amp;lt;tt&amp;gt;j&amp;lt;/tt&amp;gt; liegen die Indizes &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt; im Speicher hintereinander. Deshalb ist es günstig, in der inneren Schleife über &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt; zu iterieren:&lt;br /&gt;
       for j in range(N):&lt;br /&gt;
           jN = j*N&lt;br /&gt;
           for i in range(N):&lt;br /&gt;
               a[i + jN] = 0.0&lt;br /&gt;
           a[j + jN] = 1.0&lt;br /&gt;
:Die umgekehrte Reihenfolge der Schleifen ist hingegen ungünstig&lt;br /&gt;
       for i in range(N):&lt;br /&gt;
           for j in range(N):&lt;br /&gt;
               a[i + j*N] = 0.0&lt;br /&gt;
           a[i + i*N] = 1.0&lt;br /&gt;
:Jetzt werden in der inneren Schleife stets N Datenelemente übersprungen. Besonders bei großem N muss man daher häufig den Cache neu füllen, was bei der ersten Implementation nicht notwendig war.  (Außerdem verliert man hier die Optimierung &amp;lt;tt&amp;gt;jN = j*N&amp;lt;/tt&amp;gt;, die jetzt nicht mehr möglich ist.)&lt;br /&gt;
&lt;br /&gt;
Als Faustregel kann man durch Optimierung eine Verdoppelung der Geschwindigkeit erreichen (in Ausnahmefällen auch mehr). Benötigt man stärkere Verbesserungen, muss man wohl oder übel einen besseren Algorithmus oder einen schnelleren Computer verwenden.&lt;br /&gt;
&lt;br /&gt;
== Algorithmen-Komplexität ==&lt;br /&gt;
&lt;br /&gt;
Komplexitätsbetrachtungen ermöglichen den Vergleich der prinzipiellen Eigenschaften von Algorithmen unabhängig von einer Implementation, Umgebung etc.&lt;br /&gt;
      &lt;br /&gt;
Eine einfache Möglichkeit ist das Zählen der Aufrufe einer Schlüsseloperation. Beispiel Sortieren:&lt;br /&gt;
* Anzahl der Vergleiche&lt;br /&gt;
* Anzahl der Vertauschungen&lt;br /&gt;
&lt;br /&gt;
=== Beispiel: Selection Sort ===&lt;br /&gt;
&lt;br /&gt;
  for i in range(len(a)-1):&lt;br /&gt;
    max = i&lt;br /&gt;
    for j in range(i+1, len(a)):&lt;br /&gt;
      if a[j] &amp;lt; a[max]:&lt;br /&gt;
        max = j&lt;br /&gt;
    a[max], a[i] = a[i], a[max]      # swap&lt;br /&gt;
&lt;br /&gt;
*Anzahl der Vergleiche: Ein Vergleich in jedem Durchlauf der inneren Schleife. Es ergibt sich folgende Komplexität:&lt;br /&gt;
*:Ingesamt &amp;lt;math&amp;gt;\sum_{i=0}^{N-2} \sum_{j=i+1}^{N-1}1 = \frac{N}{2} (N-1) \!&amp;lt;/math&amp;gt; Vergleiche.&lt;br /&gt;
&lt;br /&gt;
*Anzahl der Vertauschungen (swaps): Eine Vertauschung pro Durchlauf der äußeren Schleife:&lt;br /&gt;
*:Insgesamt &amp;lt;math&amp;gt;N-1 \!&amp;lt;/math&amp;gt; Vertauschungen&lt;br /&gt;
&lt;br /&gt;
Die Komplexität wird durch die Operationen bestimmt, die am häufigsten ausgeführt werden, hier also die Anzahl der Vergleiche. Die Anzahl der Vertauschungen ist hingegen kein geeignetes Kriterium für die Komplexität von selection sort, weil der Aufwand in der inneren Schleife ignoriert würde.&lt;br /&gt;
&lt;br /&gt;
=== Fallunterscheidung: Worst und Average Case ===&lt;br /&gt;
&lt;br /&gt;
Die Komplexität ist in der Regel eine Funktion der Eingabegröße (Anzahl der Eingabebits, Anzahl der Eingabeelemente). Sie kann aber auch von der Art der Daten abhängen, nicht nur von der Menge, z.B. vorsortierte Daten bei Quicksort. Um von der Art der Daten unabhängig zu werden, kann man zwei Fälle der Komplexität unterscheiden:&lt;br /&gt;
      &lt;br /&gt;
* Komplexität im ungünstigsten Fall &lt;br /&gt;
*: Der ungünstigste Fall ist die Eingabe gegebener Länge, für die der Algorithmus am langsamsten ist. Der Nachteil dieser Methode besteht darin, dass dieser ungünstige Fall in der Praxis vielleicht gar nicht oder nur selten vorkommt, so dass sich der Algorithmus in Wirklichkeit besser verhält als man nach dieser Analyse erwarten würde. Beim Quicksort-Algorithmus mit zufälliger Wahl des Pivot-Elements müsste z.B. stets das kleinste oder größte Element des aktuellen Intervalls als Pivot-Element gewählt werden, was äußerst unwahrscheinlich ist.&lt;br /&gt;
* Komplexität im durchschnittlichen/typischen Fall&lt;br /&gt;
*: Der typische Fall ist die mittlere Komplexität des Algorithmus über alle möglichen Eingaben. Dazu muss man die Wahrscheinlichkeit jeder möglichen Eingabe kennen, und berechnet dann die mittlere Laufzeit über dieser Wahrscheinlichkeitsverteilung. Leider ist die Wahrscheinlichkeit der Eingaben oft nicht bekannt, so dass man geeignete Annahmen treffen muss. Bei Sortieralgorithmen können z.B. alle möglichen Permutationen des Eingabearrays als gleich wahrscheinlich angenommen werden, und der typische Fall ist dann die mittlere Komplexität über alle diese Eingaben. Oft hat man jedoch in der Praxis andere Wahrscheinlichkeitsverteilungen, z.B. sind die Daten oft &amp;quot;fast sortiert&amp;quot; (nur wenige Elemente sind an der falschen Stelle). Dann verhält sich der Algorithmus ebenfalls anders als vorhergesagt.&lt;br /&gt;
&lt;br /&gt;
Wir beschränken uns in dieser Vorlesung auf die Komplexität im ungünstigseten Fall. '''Exakte''' Formeln für Komplexität sind aber auch dann schwer zu gewinnen, wie das folgende Beispiel zeigt:&lt;br /&gt;
&lt;br /&gt;
=== Beispiele aus den Übungen (Gemessene Laufzeiten für Mergesort/Selectionsort) ===&lt;br /&gt;
&lt;br /&gt;
* Mergesort: &amp;lt;math&amp;gt;\frac{0,977N\log N}{\log 2} + 0,267N-4.39 \!&amp;lt;/math&amp;gt;&lt;br /&gt;
*: andere Lösung: &amp;lt;math&amp;gt;1140 N\log(N) - 1819N + 6413 \!&amp;lt;/math&amp;gt;&lt;br /&gt;
* Selectionsort: &amp;lt;math&amp;gt;\frac{1}{2}N^2 - \frac{1}{2N} - 10^{-12} \!&amp;lt;/math&amp;gt;&lt;br /&gt;
*: andere Lösung: &amp;lt;math&amp;gt;1275N^2 - 116003^N + 11111144 \!&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Aus diesen Formeln wird nicht offensichtlich, welcher Algorithmus besser ist.&lt;br /&gt;
Näherung: Betrachte nur '''sehr große Eingaben''' (meist sind alle Algorithmen schnell genug für kleine Eingaben). Dieses Vorgehen wird als '''Asymptotische Komplexität''' bezeichnet (N gegen unendlich).&lt;br /&gt;
&lt;br /&gt;
=== Asymptotische Komplexität am Beispiel Polynom ===&lt;br /&gt;
&lt;br /&gt;
Polynom: &amp;lt;math&amp;gt;a\,x^2+b\,x+c=p\!&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;x \!&amp;lt;/math&amp;gt; sei die Eingabegröße, und wir betrachten die Entwicklung von &amp;lt;math&amp;gt;p \!&amp;lt;/math&amp;gt; in Abhängigkeit von &amp;lt;math&amp;gt;x \!&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;math&amp;gt;x=0 \!&amp;lt;/math&amp;gt;&lt;br /&gt;
*: &amp;lt;math&amp;gt;p=c \!&amp;lt;/math&amp;gt;&lt;br /&gt;
* &amp;lt;math&amp;gt;x=1 \!&amp;lt;/math&amp;gt;&lt;br /&gt;
*: &amp;lt;math&amp;gt;p=a+b+c \!&amp;lt;/math&amp;gt;&lt;br /&gt;
* &amp;lt;math&amp;gt;x=1000 \!&amp;lt;/math&amp;gt;&lt;br /&gt;
*: &amp;lt;math&amp;gt;p=1000000a+1000b+c \approx 1000000a\!&amp;lt;/math&amp;gt;&lt;br /&gt;
* &amp;lt;math&amp;gt;x \to \infty \!&amp;lt;/math&amp;gt;&lt;br /&gt;
*: &amp;lt;math&amp;gt;p \approx x^2a\!&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Für sehr große Eingaben verlieren also ''b'' und ''c'' immer mehr an Bedeutung, so dass am Ende nur noch ''a'' für die Komplexitätsbetrachtung wichtig ist.&lt;br /&gt;
&lt;br /&gt;
== Landau-Symbole ==&lt;br /&gt;
&lt;br /&gt;
Um die asymptotische Komplexität verschiedener Algorithmen miteinander vergleichen zu können, verwendet man die sogenannten [http://de.wikipedia.org/wiki/Landau-Symbole Landau-Symbole]. Das wichtigste Landau-Symbol ist &amp;lt;math&amp;gt;\mathcal{O}&amp;lt;/math&amp;gt;, mit dem man eine ''obere Schranke'' &amp;lt;math&amp;gt;f \in \mathcal{O}(g)&amp;lt;/math&amp;gt; für die Komplexität angeben kann. &lt;br /&gt;
&lt;br /&gt;
Schreibt man &amp;lt;math&amp;gt;f \in \Omega(g)&amp;lt;/math&amp;gt;, so stellt dies eine asymptotische ''untere Schranke'' für die Funktion f dar.&lt;br /&gt;
&lt;br /&gt;
Schließlich bedeutet &amp;lt;math&amp;gt;f \in \Theta(g)&amp;lt;/math&amp;gt;, dass die Funktion f genauso schnell wie die Funktion g wächst, das heißt man hat eine asymptotisch ''scharfe Schranke'' für f. Hierzu muss sowohl &amp;lt;math&amp;gt;f\in\mathcal{O}(g)&amp;lt;/math&amp;gt; als auch &amp;lt;math&amp;gt;f \in \Omega(g)&amp;lt;/math&amp;gt; erfüllt sein. &lt;br /&gt;
&lt;br /&gt;
Im nun folgenden soll auf die verschiedenen Landau-Symbole noch näher eingegeangen werden.&lt;br /&gt;
&lt;br /&gt;
===O-Notation===&lt;br /&gt;
&lt;br /&gt;
Intuitiv gilt: Für große N dominieren die am schnellsten wachsenden Terme einer Funktion. Die Notation &amp;lt;math&amp;gt;f \in \mathcal{O}(g)&amp;lt;/math&amp;gt; (sprich &amp;quot;f ist in O von g&amp;quot; oder &amp;quot;f ist von derselben Größenordnung wie g&amp;quot;) formalisiert eine solche Abschätzung der asymptotischen Komplexität der Funktion f von oben. &lt;br /&gt;
; Asymptotische Komplexität: Für zwei Funktionen f(x) und g(x) gilt&lt;br /&gt;
::&amp;lt;math&amp;gt;f(x) \in \mathcal{O}(g(x))&amp;lt;/math&amp;gt;&lt;br /&gt;
: genau dann wenn es eine Konstante &amp;lt;math&amp;gt;c&amp;gt;0&amp;lt;/math&amp;gt; und ein Argument &amp;lt;math&amp;gt;x_0&amp;lt;/math&amp;gt; gibt, so dass &lt;br /&gt;
::&amp;lt;math&amp;gt;\forall x \ge x_0:\quad f(x) \le c\,g(x)&amp;lt;/math&amp;gt;.&lt;br /&gt;
:Die Menge &amp;lt;math&amp;gt;\mathcal{O}(g(x))&amp;lt;/math&amp;gt; aller durch g(x) abschätzbaren Funktionen ist also formal definiert durch&lt;br /&gt;
::&amp;lt;math&amp;gt;\mathcal{O}(g(x)) = \{ f(x)\ |\ \exists c&amp;gt;0: \forall x \ge x_0: 0 \le f(x) \le c\,g(x)\}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Die Idee hinter dieser Definition ist, dass g(x) eine wesentlich einfachere Funktion ist als f(x), die sich aber nach geeigneter Skalierung (Multiplikation mit c) und für große Argumente x im wesentlichen genauso wie f(x) verhält. Man kann deshalb in der Algorithmenanalyse f(x) durch g(x) ersetzen. &amp;lt;math&amp;gt;f(x) \in \mathcal{O}(g(x))&amp;lt;/math&amp;gt; spielt für Funktionen eine ähnliche Rolle wie der Operator &amp;amp;le; für Zahlen: Falls a &amp;amp;le; b gilt, kann bei einer Abschätzung von oben ebenfalls a durch b ersetzt werden.&lt;br /&gt;
&lt;br /&gt;
==== Ein einfaches Beispiel ====&lt;br /&gt;
&lt;br /&gt;
[[Image:Sqsqrt.png]]&lt;br /&gt;
&lt;br /&gt;
Rot = &amp;lt;math&amp;gt;x^2 \!&amp;lt;/math&amp;gt;&lt;br /&gt;
Blau = &amp;lt;math&amp;gt;\sqrt{x} \!&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\sqrt{x} \in \mathcal{O}(x^2)\!&amp;lt;/math&amp;gt; weil &amp;lt;math&amp;gt;\sqrt{x} \le c\,x^2\!&amp;lt;/math&amp;gt; für alle &amp;lt;math&amp;gt;x \ge x_0 = 1 \!&amp;lt;/math&amp;gt; und &amp;lt;math&amp;gt;c = 1\!&amp;lt;/math&amp;gt;, oder auch für &amp;lt;math&amp;gt;x \ge x_0 = 4 \!&amp;lt;/math&amp;gt; und &amp;lt;math&amp;gt;c = 1/16&amp;lt;/math&amp;gt; (die Wahl von c und x&amp;lt;sub&amp;gt;0&amp;lt;/sub&amp;gt; in der Definition von O(.) ist beliebig, solange die Bedingungen erfüllt sind).&lt;br /&gt;
&lt;br /&gt;
==== Komplexität bei kleinen Eingaben ==== &lt;br /&gt;
&lt;br /&gt;
Algorithmus 1: &amp;lt;math&amp;gt;\mathcal{O}(N^2) \!&amp;lt;/math&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
Algorithmus 2: &amp;lt;math&amp;gt;\mathcal{O}(N\log{N}) \!&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Algorithmus 2 ist schneller (von geringerer Komplexität) für große Eingaben, aber bei kleinen Eingaben (insbesondere, wenn der Algorithmus in einer Schleife immer wieder mit kleinen Eingaben aufgerufen wird) könnte Algorithmus 1 schneller sein, falls der in der &amp;lt;math&amp;gt;\mathcal{O}&amp;lt;/math&amp;gt;-Notation verborgene konstante Faktor ''c'' bei Algorithmus 2 einen wesentlich größeren Wert hat als bei Algorithmus 1.&lt;br /&gt;
&lt;br /&gt;
==== Eigenschaften der O-Notation (Rechenregeln) ==== &lt;br /&gt;
&lt;br /&gt;
# Transitiv:&lt;br /&gt;
#: &amp;lt;math&amp;gt;f(x) \in \mathcal{O}(g(x)) \land g(x) \in \mathcal{O}(h(x)) \to f(x) \in \mathcal{O}(h(x)) \!&amp;lt;/math&amp;gt;           &lt;br /&gt;
# Additiv:&lt;br /&gt;
#: &amp;lt;math&amp;gt;f(x) \in \mathcal{O}(h(x)) \land g(x) \in \mathcal{O}(h(x)) \to f(x) + g(x) \in \mathcal{O}(h(x)) \!&amp;lt;/math&amp;gt;           &lt;br /&gt;
# Für Monome gilt:&lt;br /&gt;
#: &amp;lt;math&amp;gt;x^k \in \mathcal{O}(x^k)&amp;lt;/math&amp;gt; und&lt;br /&gt;
#: &amp;lt;math&amp;gt;x^k \in \mathcal{O}(x^{k+j}), \forall j \ge 0 \!&amp;lt;/math&amp;gt;&lt;br /&gt;
# Multiplikation mit einer Konstanten:&lt;br /&gt;
#: &amp;lt;math&amp;gt;f(x) \in \mathcal{O}(g(x)) \to c\,f(x) \in \mathcal{O}(g(x))\!&amp;lt;/math&amp;gt;&lt;br /&gt;
#: andere Schreibweise:&lt;br /&gt;
#: &amp;lt;math&amp;gt;f(x) = c\,g(x) \to f(x) \in \mathcal{O}(g(x))\!&amp;lt;/math&amp;gt;&lt;br /&gt;
# Folgerung aus 3. und 4. für Polynome: &lt;br /&gt;
#: &amp;lt;math&amp;gt;a_0+a_1\,x + ... + a_n\,x^n \in \mathcal{O}(x^n)\!&amp;lt;/math&amp;gt;&lt;br /&gt;
#: Beispiel: &amp;lt;math&amp;gt;a\,x^2+b\,x+c \in \mathcal{O}(x^2)\!&amp;lt;/math&amp;gt;&lt;br /&gt;
# Logarithmus:&lt;br /&gt;
#: &amp;lt;math&amp;gt;a, b &amp;gt; 1\!&amp;lt;/math&amp;gt;&lt;br /&gt;
#: &amp;lt;math&amp;gt;\log_{a}{x} \in \mathcal{O}(\log_{b}{x})\!&amp;lt;/math&amp;gt;&lt;br /&gt;
#: Die Basis des Logarithmus spielt also keine Rolle.&lt;br /&gt;
#: Beweis hierfür:&lt;br /&gt;
#:: &amp;lt;math&amp;gt;\log_{a}{x} = \frac{\log_{b}{x}}{\log_{b}{a}}\!&amp;lt;/math&amp;gt;&lt;br /&gt;
#:: Mit &amp;lt;math&amp;gt;c = 1 / \log_{b}{a}\,&amp;lt;/math&amp;gt; gilt: &amp;lt;math&amp;gt;\log_{a}{x} = c\,\log_{b}{x}\!&amp;lt;/math&amp;gt;.&lt;br /&gt;
#:: Wird hier die (zweite) Regel für Multiplikation mit einer Konstanten angewendet, fällt der konstante Faktor weg, also &amp;lt;math&amp;gt;\log_{a}{x} \in \mathcal{O}(\log_{b}{x})\!&amp;lt;/math&amp;gt;.&lt;br /&gt;
#: Insbesondere gilt auch &amp;lt;math&amp;gt;\log_{a}{x} \in \mathcal{O}(\log_{2}{x})\!&amp;lt;/math&amp;gt;, es kann also immer der 2er Logarithmus verwendet werden.&lt;br /&gt;
&lt;br /&gt;
==== O-Kalkül ==== &lt;br /&gt;
&lt;br /&gt;
Das O-Kalkül definiert wichtige Vereinfachungsregeln for Ausdrücke in O-Notation (Beweise: siehe Übungsaufgabe):&lt;br /&gt;
&lt;br /&gt;
# &amp;lt;math&amp;gt;f(x) \in \mathcal{O}(f(x))\!&amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt;\mathcal{O}(\mathcal{O}(f(x))) \in \mathcal{O}(f(x))\!&amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt;c\,\mathcal{O}(f(x)) \in \mathcal{O}(f(x))\,&amp;lt;/math&amp;gt; für jede Konstante ''c''&lt;br /&gt;
# &amp;lt;math&amp;gt;\mathcal{O}(f(x))+c \in \mathcal{O}(f(x))\,&amp;lt;/math&amp;gt; für jede Konstante ''c''&lt;br /&gt;
# Sequenzregel:&lt;br /&gt;
#: Wenn zwei nacheinander ausgeführte Programmteile die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(f(x))&amp;lt;/math&amp;gt; bzw. &amp;lt;math&amp;gt;\mathcal{O}(g(x))&amp;lt;/math&amp;gt; haben, gilt für beide gemeinsam:&lt;br /&gt;
#: &amp;lt;math&amp;gt;\mathcal{O}(f(x)) + \mathcal{O}(g(x)) \in \mathcal{O}(f(x))&amp;lt;/math&amp;gt; falls &amp;lt;math&amp;gt;g(x) &amp;lt; \mathcal{O}(f(x))&amp;lt;/math&amp;gt; bzw.&lt;br /&gt;
#: &amp;lt;math&amp;gt;\mathcal{O}(f(x)) + \mathcal{O}(g(x)) \in \mathcal{O}(g(x))\!&amp;lt;/math&amp;gt; falls &amp;lt;math&amp;gt;f(x) &amp;lt; \mathcal{O}(g(x))&amp;lt;/math&amp;gt;.&lt;br /&gt;
#: Informell schreibt man auch: &amp;lt;math&amp;gt;\mathcal{O}(f(x)) + \mathcal{O}(g(x)) \in \mathcal{O}(max(f(x), g(x)))\!&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Schachtelungsregel bzw. Aufrufregel:&lt;br /&gt;
#: Wenn in einer geschachtelten Schleife die äußere Schleife die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(f(x))&amp;lt;/math&amp;gt; hat, und die innere &amp;lt;math&amp;gt;\mathcal{O}(g(x))&amp;lt;/math&amp;gt;, gilt für beide gemeinsam:&lt;br /&gt;
#: &amp;lt;math&amp;gt;\mathcal{O}(f(x)) * \mathcal{O}(g(x)) \in \mathcal{O}(f(x) * g(x))\!&amp;lt;/math&amp;gt;.&lt;br /&gt;
#: Gleiches gilt wenn eine Funktion &amp;lt;math&amp;gt;\mathcal{O}(f(x))&amp;lt;/math&amp;gt;-mal aufgerufen wird, und die Komplexität der Funktion selbst &amp;lt;math&amp;gt;\mathcal{O}(g(x))&amp;lt;/math&amp;gt; ist.&lt;br /&gt;
&lt;br /&gt;
;Beispiel für 5.: Beide Schleifen haben die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt;. Dies gilt auch für ihre Hintereinanderausführung:&lt;br /&gt;
      for i in range(N):&lt;br /&gt;
          a[i] = i&lt;br /&gt;
      for i in range(N):&lt;br /&gt;
          print a[i]&lt;br /&gt;
;Beispiele für 6.: Beide Schleifen haben die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt;. Ihre Verschachtelung hat daher die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(N^2)&amp;lt;/math&amp;gt;. &lt;br /&gt;
      for i in range(N):&lt;br /&gt;
          for j in range(N):&lt;br /&gt;
              a[i*N + j] = i+j&lt;br /&gt;
: Dies gilt ebenso, wenn statt der inneren Schleife eine Funktion mit Komplexität &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; ausgeführt wird:&lt;br /&gt;
      for i in range(N):&lt;br /&gt;
          a[i] = foo(i, N)  # &amp;lt;math&amp;gt;\mathrm{foo}(i, N) \in \mathcal{O}(N)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== O-Kalkül auf das Beispiel des Selectionsort angewandt ==== &lt;br /&gt;
&lt;br /&gt;
Selectionsort: Wir hatten gezeigt dass &amp;lt;math&amp;gt;f(N) = \frac{N^2}{2} - \frac{N}{2}&amp;lt;/math&amp;gt;. Nach der Regel für Polynome vereinfacht sich dies zu &amp;lt;math&amp;gt;f(N) \in \mathcal{O}\left(\frac{N^2}{2}\right) = \mathcal{O}(N^2)\!&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Alternativ via Schachtelungsregel:&lt;br /&gt;
: Die äußere Schleife wird (''N''-1)-mal durchlaufen: &amp;lt;math&amp;gt;N-1 \in \mathcal{O}(N)&amp;lt;/math&amp;gt;&lt;br /&gt;
: Die innere Schleife wird (''N-i''-1)-mal durchlaufen. Das sind im Mittel ''N''/2 Durchläufe: &amp;lt;math&amp;gt;N/2 \in \mathcal{O}(N)&amp;lt;/math&amp;gt;&lt;br /&gt;
: Zusammen: &amp;lt;math&amp;gt;\mathcal{O}(N)*\mathcal{O}(N) \in \mathcal{O}(N^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nach beiden Vorgehensweisen kommen wir zur Schlussfolgerung, dass der Selectionsort die asymptotische Komplexität &amp;lt;math&amp;gt;\mathcal{O}(N^2)\!&amp;lt;/math&amp;gt; besitzt.&lt;br /&gt;
&lt;br /&gt;
==== Zusammenhang zwischen Komplexität und Laufzeit ==== &lt;br /&gt;
&lt;br /&gt;
Wenn eine Operation 1ms dauert, erreichen Algorithmen verschiedener Komplexität folgende Leistungen (wobei angenommen wird, dass der in der &amp;lt;math&amp;gt;\mathcal{O}&amp;lt;/math&amp;gt;-Notation verborgene konstante Faktor immer etwa gleich 1 ist):&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align:left&amp;quot; cellpadding=&amp;quot;7&amp;quot;&lt;br /&gt;
|+&lt;br /&gt;
|-&lt;br /&gt;
! Komplexität !! Operationen in 1s !! Operationen in 1min !! Operationen in 1h&lt;br /&gt;
|-&lt;br /&gt;
! &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt;&lt;br /&gt;
| 1000 || 60.000 || 3.600.000&lt;br /&gt;
|-&lt;br /&gt;
! &amp;lt;math&amp;gt;\mathcal{O}(N\log_2{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
| 140 || 4895 || 204094&lt;br /&gt;
|-&lt;br /&gt;
! &amp;lt;math&amp;gt;\mathcal{O}(N^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
| 32 || 245 || 1898&lt;br /&gt;
|-&lt;br /&gt;
! &amp;lt;math&amp;gt;\mathcal{O}(N^3)&amp;lt;/math&amp;gt;&lt;br /&gt;
| 10 || 39 || 153&lt;br /&gt;
|-&lt;br /&gt;
! &amp;lt;math&amp;gt;\mathcal{O}(2^N)&amp;lt;/math&amp;gt;&lt;br /&gt;
| 10 || 16 || 21&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==== Exponentielle Komplexität ==== &lt;br /&gt;
Der letzte Fall &amp;lt;math&amp;gt;\mathcal{O}(2^N)&amp;lt;/math&amp;gt; ist von exponentieller Komplexität. Das bedeutet, dass eine Verdopplung des Aufwands nur bewirkt, dass die maximale Problemgröße um eine Konstante wächst. Algorithmen mit exponentieller (oder noch höherer) Komplexität werden deshalb als '''ineffizient''' bezeichnet. Algorithmen mit höchstens polynomieller Komplexität gelten hingegen als effizient.&lt;br /&gt;
&lt;br /&gt;
In der Praxis sind allerdings auch polynomielle Algorithmen mit hohem Exponenten meist zu langsam. Als Faustregel kann man eine praktische Grenze von &amp;lt;math&amp;gt;\mathcal{O}(N^3)&amp;lt;/math&amp;gt; ansehen. Bei einer Komplexität von &amp;lt;math&amp;gt;\mathcal{O}(N^3)&amp;lt;/math&amp;gt; bewirkt ein verdoppelter Aufwand immer noch eine Steigerung der maximalen Problemgröße um den Faktor &amp;lt;math&amp;gt;\sqrt[3]{2}&amp;lt;/math&amp;gt; (also eine ''multiplikative'' Vergrößerung um ca. 25%, statt nur einer additiven Vergrößerung wie bei exponentieller Komplexität).&lt;br /&gt;
&lt;br /&gt;
===&amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;- Notation===&lt;br /&gt;
&lt;br /&gt;
Genauso wie &amp;lt;math&amp;gt;f \in \mathcal{O}(g)&amp;lt;/math&amp;gt; eine Art &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt;-Operator für Funktionen ist, definiert &amp;lt;math&amp;gt;f \in \Omega(g) &amp;lt;/math&amp;gt; eine Abschätzung von unten, analog zum &amp;lt;math&amp;gt;\ge&amp;lt;/math&amp;gt;-Operator für Zahlen. Formal kann man &amp;lt;math&amp;gt;f(N) \in \Omega(g(N)) &amp;lt;/math&amp;gt; genau dann schreiben, falls es eine Konstante &amp;lt;math&amp;gt; c &amp;gt; 0 &amp;lt;/math&amp;gt; gibt, so dass &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; f(N) \ge c \cdot g(N) &amp;lt;/math&amp;gt; für &amp;lt;math&amp;gt; N  \ge N_0 &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
gilt.&lt;br /&gt;
Man verwendet diese Notation also um abzuschätzen, wie groß der Aufwand (die Komplexität) für einen bestimmten Algorithmus ''mindestens'' ist und nicht ''höchstens'', was man mit der &amp;lt;math&amp;gt;\mathcal{O}&amp;lt;/math&amp;gt; - Notation ausdrücken würde.&lt;br /&gt;
&lt;br /&gt;
Ein praktisches Beispiel für eine Anwendung der &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;- Notation wäre die Fragestellung, ob es ''prinzipiell'' einen besseren Algorithmus für ein bestimmtes Problem gibt. Wie später im Abschnitt [[Suchen#Sortieren_als_Suchproblem|Sortieren als Suchproblem]] gezeigt wird, ist das Sortieren eines Arrays durch paarweise Vergleiche von Elementen immer mindestens von der Komplexität &amp;lt;math&amp;gt; \Omega(N\cdot \ln N) &amp;lt;/math&amp;gt;, was konkret bedeutet, dass kein Sortieralgorithmus, der nach diesem Prinzip arbeitet, jemals eine geringere Komplexität als beispielsweise Merge-Sort haben wird. Natürlich kann man den entsprechenden Sortieralgorithmus, also Merge-Sort zum Beispiel, unter Umständen noch optimieren, aber die Komplexität wird erhalten bleiben. Mit diesem Wissen kann man sich viel (vergebliche) Arbeit sparen.&lt;br /&gt;
&lt;br /&gt;
===&amp;lt;math&amp;gt;\Theta&amp;lt;/math&amp;gt;- Notation===&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;math&amp;gt;f(N) \in \Theta(g(N))&amp;lt;/math&amp;gt; ist eine scharfe Abschätzung der asymptotischen Komplexität einer Funktion f. &lt;br /&gt;
&lt;br /&gt;
Damit dies gilt, muss &amp;lt;math&amp;gt;f(N) \in \mathcal{O}(g(N))&amp;lt;/math&amp;gt; und ''gleichzeitig'' &amp;lt;math&amp;gt;f(N) \in \Omega(g(N))&amp;lt;/math&amp;gt; erfüllt sein.&lt;br /&gt;
&lt;br /&gt;
Dies ist natürlich auch die beste Abschätzung der asymptotischen Komplexität einer Funktion f. Formal bedeutet &amp;lt;math&amp;gt;f(N) \in \Theta(g(N))&amp;lt;/math&amp;gt; dass es zwei Konstanten &amp;lt;math&amp;gt; c_1 &amp;lt;/math&amp;gt; und &amp;lt;math&amp;gt; c_2 &amp;lt;/math&amp;gt;, beide größer als Null, gibt, so dass für alle &amp;lt;math&amp;gt; N \geq N_0 &amp;lt;/math&amp;gt; gilt: &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; c_1 \cdot g(N) \leq f(N) \leq c_2 \cdot g(N) &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
In der Praxis wird manchmal statt der &amp;lt;math&amp;gt;\Theta&amp;lt;/math&amp;gt;-Notation auch dann die &amp;lt;math&amp;gt;\mathcal{O}&amp;lt;/math&amp;gt;-Notation benutzt, wenn eine scharfe Schranke ausgedrückt werden soll. Dies ist zwar formal nicht korrekt, aber man kann die intendierte Bedeutung meist aus dem Kontext erschließen.&lt;br /&gt;
&lt;br /&gt;
== Komplexitätsvergleich zweier Algorithmen ==&lt;br /&gt;
&lt;br /&gt;
In diesem Abschnitt wollen wir der Frage nachgehen, wie ein formaler Beweis für die Behauptung &amp;lt;math&amp;gt; f(N) \in \mathcal{O}(g(N))&amp;lt;/math&amp;gt; geschehen kann. Hierbei werden zwei Beweismethoden vorgestellt werden, und zwar der '''Beweis über die Definition der Komplexität''' sowie der '''Beweis durch Dividieren'''.&lt;br /&gt;
&lt;br /&gt;
===Beweis über die Definition der asymptotischen Komplexität===&lt;br /&gt;
&lt;br /&gt;
Die Definition der asymptotischen Komplexität &amp;lt;math&amp;gt;f(N) \in \mathcal{O}(g(N))&amp;lt;/math&amp;gt; war: &lt;br /&gt;
&lt;br /&gt;
Es gibt eine Konstante &amp;lt;math&amp;gt; c &amp;gt; 0 &amp;lt;/math&amp;gt;, so dass &amp;lt;math&amp;gt; f(N) \le c \cdot g(N) &amp;lt;/math&amp;gt; für &amp;lt;math&amp;gt; N  \ge N_0 &amp;lt;/math&amp;gt; erfüllt ist. &lt;br /&gt;
&lt;br /&gt;
Um also die die asymptotische Komplexität &amp;lt;math&amp;gt;f(N) \in \mathcal{O}(g(N))&amp;lt;/math&amp;gt; zu beweisen, muss man die oben erwähnten Konstanten c und &amp;lt;math&amp;gt; N_0 &amp;lt;/math&amp;gt; finden, so dass &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; f(N) \leq c \cdot g(N) &amp;lt;/math&amp;gt; für alle &amp;lt;math&amp;gt; N \ge N_0 &amp;lt;/math&amp;gt; erfüllt ist. &lt;br /&gt;
&lt;br /&gt;
Dies geschieht zweckmäßigerweise mit dem Beweisprinzip der ''vollständigen Induktion''. Hierbei ist zu zeigen, dass&lt;br /&gt;
# &amp;lt;math&amp;gt; f(N_0) \leq g(N_0) &amp;lt;/math&amp;gt; für die eine zu bestimmende Konstante &amp;lt;math&amp;gt; N_0 &amp;lt;/math&amp;gt; gilt (''Induktionsanfang'') und &lt;br /&gt;
# falls &amp;lt;math&amp;gt; f(N) \leq g(N) &amp;lt;/math&amp;gt;, dann auch &amp;lt;math&amp;gt; f(N+1) \leq g(N+1) &amp;lt;/math&amp;gt; (''Induktionsschritt'') gilt.&lt;br /&gt;
&lt;br /&gt;
===Beweis durch Dividieren===&lt;br /&gt;
&lt;br /&gt;
Hierbei wählt man eine Konstante c und zeigt, dass &amp;lt;math&amp;gt; \lim_{N \rightarrow \infty} \frac{f(N)}{c \cdot g(N)} \leq 1 &amp;lt;/math&amp;gt; gilt (für die O-Notation, bei &amp;amp;Omega;-Notation gilt entsprechend &amp;lt;math&amp;gt;\geq 1 &amp;lt;/math&amp;gt;). Man kann dies auch als alternative Definition der Komplexität verwenden.&lt;br /&gt;
&lt;br /&gt;
Als Beispiel betrachten wir die beiden Funktionen &amp;lt;math&amp;gt; f(N) = N \,\lg N &amp;lt;/math&amp;gt; und &amp;lt;math&amp;gt; g(N) = N^2 &amp;lt;/math&amp;gt; und wollen zeigen, dass &amp;lt;math&amp;gt;f(N) \in \mathcal{O}(g(N))&amp;lt;/math&amp;gt; gilt. &lt;br /&gt;
&lt;br /&gt;
Als Konstante c wählen wir &amp;lt;math&amp;gt; c = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \lim_{N \rightarrow \infty} \frac{f(N)}{g(N)} = \lim_{N \rightarrow \infty} \frac{\lg N}{N} = \frac{\infty}{\infty} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Unbestimmte Ausdrücke der Form &lt;br /&gt;
&amp;lt;math&amp;gt; \lim_{x \rightarrow x_0} \frac{f(x)}{g(x)} &amp;lt;/math&amp;gt;,&lt;br /&gt;
in denen sowohl &amp;lt;math&amp;gt; f(x) &amp;lt;/math&amp;gt; als auch &amp;lt;math&amp;gt; g(x) &amp;lt;/math&amp;gt; mit &amp;lt;math&amp;gt; x \rightarrow x_0 &amp;lt;/math&amp;gt; gegen Null oder gegen Unendlich streben, kann man manchmal mit den Regeln von [http://de.wikipedia.org/wiki/L%27Hospital%27sche_Regel ''l'Hospital''] berechnen. Danach darf man die Funktionen f und g zur Berechnung des unbestimmten Ausdrucks durch ihre k-ten Ableitungen ersetzen:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \lim_{x \rightarrow x_0} \frac{f(x)}{g(x)} = \lim_{x \rightarrow x_0} \frac{f^{(k)}(x)}{g^{(k)}(x)} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In unserem Fall verwenden wir die erste Ableitung und erhalten:&lt;br /&gt;
&amp;lt;math&amp;gt; \lim_{N \rightarrow \infty} \frac{f'(x)}{g'(x)} = \lim_{N \rightarrow \infty} \frac{1/N}{1} \rightarrow 0 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Damit wurde &amp;lt;math&amp;gt;f(N) \in \mathcal{O}(g(N))&amp;lt;/math&amp;gt;, also &amp;lt;math&amp;gt;N \lg N \in \mathcal{O}(N^2)&amp;lt;/math&amp;gt; gezeigt.&lt;br /&gt;
&lt;br /&gt;
Man beachte hierbei, dass &amp;lt;math&amp;gt;N \lg N \in \mathcal{O}(N^2)&amp;lt;/math&amp;gt; keine enge Grenze für die Komplexität von &amp;lt;math&amp;gt;N \,\lg N&amp;lt;/math&amp;gt; darstellt, da der Grenzwert &amp;lt;math&amp;gt; \lim_{N \rightarrow \infty} \frac{f'(x)}{g'(x)}\, &amp;lt;/math&amp;gt; gegen 0 und nicht gegen eine von Null verschiedene Konstante strebt. In diesem Fall haben wir die Komplexität von &amp;lt;math&amp;gt;N \cdot \lg N &amp;lt;/math&amp;gt; also nur nach oben abschätzen können.&lt;br /&gt;
&lt;br /&gt;
===Beispiel für den Komplexitätsvergleich: Gleitender Mittelwert (Running Average)===&lt;br /&gt;
&lt;br /&gt;
Wir berechnen für ein gegebenes Array &amp;lt;tt&amp;gt;a&amp;lt;/tt&amp;gt; einen gleitenden Mittelwert über &amp;lt;tt&amp;gt;k&amp;lt;/tt&amp;gt; Elemente:&amp;lt;br/&amp;gt;&lt;br /&gt;
::&amp;lt;math&amp;gt;r_i = \frac{1}{k} \sum_{j=i-k+1}^i a_j&amp;lt;/math&amp;gt; &amp;lt;br/&amp;gt;&lt;br /&gt;
Das heisst, für jedes &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt; mitteln wir die letzten &amp;lt;tt&amp;gt;k&amp;lt;/tt&amp;gt; Elemente von &amp;lt;tt&amp;gt;a&amp;lt;/tt&amp;gt; und schreiben das Ergebnis in &amp;lt;tt&amp;gt;r[i]&amp;lt;/tt&amp;gt;. Diese Operation ist z.B. bei Börsenkursen wichtig: Neben dem aktuellen Kurs für jeden Tag wird dort meist auch der gleitende Mittelwert der letzten 30 Tage sowie der letzten 200 Tage angegeben. In diesen Mittelwerten erkennt man besser die langfristige Tendenz, weil die täglichen Schwankungen herausgemittelt werden. Wir nehmen außerdem an, dass&lt;br /&gt;
* Array-Zugriff hat eine Komplexit&amp;amp;auml;t von O(1)&lt;br /&gt;
* &amp;lt;math&amp;gt;k \ll N&amp;lt;/math&amp;gt;, d.h. &amp;lt;math&amp;gt;N-k\approx N&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Die beiden folgenden Algorithmen berechnen die Mittelwerte auf unterschiedliche Art. Der linke folgt der obigen Definition durch eine Summe, während der rechte inkrementell arbeitet: Man kann den Bereich der &amp;lt;tt&amp;gt;k&amp;lt;/tt&amp;gt; letzten Werte als Fenster betrachten, das über das Array &amp;lt;tt&amp;gt;a&amp;lt;/tt&amp;gt; geschoben wird. Schiebt man das Fenster ein Element weiter, fällt links ein Element heraus, und rechts kommt eins hinzu. Man muss also nicht jedes Mal die Summe neu berechnen, sondern kann den vorigen Wert aktualisieren. Wir werden sehen, dass dies Folgen für die Komplexität des Algorithmus hat.&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;0&amp;quot; cellpadding=&amp;quot;2&amp;quot; &lt;br /&gt;
|-&lt;br /&gt;
! Programmzeile&lt;br /&gt;
! Version 1: O(N * k)&lt;br /&gt;
! Komplexit&amp;amp;auml;t&lt;br /&gt;
! Version 2: O(N)&lt;br /&gt;
! Komplexit&amp;amp;auml;t&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
1.&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;tt&amp;gt;r = [0] * len(a)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(N)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;tt&amp;gt;r = [0] * len(a)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(N)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
2.&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;tt&amp;gt;if k &amp;gt; len(a):&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(1)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;tt&amp;gt;if k &amp;gt; len(a):&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(1)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
3.&lt;br /&gt;
|&lt;br /&gt;
:: &amp;lt;tt&amp;gt;raise RuntimeError (&amp;quot;k zu gro&amp;amp;szlig;&amp;quot;)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
|&lt;br /&gt;
:: &amp;lt;tt&amp;gt;raise RuntimeError (&amp;quot;k zu gro&amp;amp;szlig;&amp;quot;)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
4.&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;tt&amp;gt;for j in range(k-1, len(a)):&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;center&amp;gt;O(N-k+1) = '''O(N)'''&amp;lt;/center&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;tt&amp;gt;for i in range(k):&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(k)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
5.&lt;br /&gt;
|&lt;br /&gt;
:: &amp;lt;tt&amp;gt;for i in range(j-k+1, j+1):&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(k)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|&lt;br /&gt;
:: &amp;lt;tt&amp;gt;r[k-1] += a[i]&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(1)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
6.&lt;br /&gt;
|&lt;br /&gt;
:::: &amp;lt;tt&amp;gt;r[j] += a[i]&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(1)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;tt&amp;gt;for j in range(k, len(a)):&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;center&amp;gt;O(N-k+1) = '''O(N)'''&amp;lt;/center&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
7.&lt;br /&gt;
|&lt;br /&gt;
:: &amp;lt;tt&amp;gt;r[j] /= float(k)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(1)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|&lt;br /&gt;
:: &amp;lt;tt&amp;gt;r[j] = (a[j] - a[j-k] + r[j-1])&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(1)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
8.&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;tt&amp;gt;return r&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(1)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;tt&amp;gt;for j in range(len(a)):&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(N)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
9.&lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
|&lt;br /&gt;
:: &amp;lt;tt&amp;gt;r[j] /= float(k)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(1)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
10.&lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;tt&amp;gt;return r&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(1)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Wir zeigen unten dass Version 2 eine geringere Komplexit&amp;amp;auml;t besitzt, obwohl sie mehr Zeilen ben&amp;amp;ouml;tigt.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Wir haben in der Tabelle die Komplexität jeder Zeile für sich angegeben. Einfache Anweisungen (Berechnungen, Lese- und Schreibzugriffe auf das Array, Zuweiseungen) haben konstante Komplexität, die Komplexität des Schleifenkopfes allein (also der &amp;lt;tt&amp;gt;for&amp;lt;/tt&amp;gt;-Anweisung ohne den eingerückten Schleifenkörper) entspricht der Anzahl der Durchläufe. Wir müssen jetzt noch die Verschachtelung der Schleifen und die Nacheinanderausführung von Anweisungen berücksichtigen. &lt;br /&gt;
&lt;br /&gt;
====Berechnung der Komplexität von Version 1====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;small&amp;gt;(Wiederholung der Rechenregeln: siehe Abschnitt [[Effizienz#O-Notation|O-Notation]])&amp;lt;/small&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Wir betrachten zunächst die innere Schleife (Zeilen 5 und 6 von Version 1):&lt;br /&gt;
&lt;br /&gt;
Der Schleifenkopf (Zeile 5) hat die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(k)&amp;lt;/math&amp;gt;, weil die Schleife k-mal durchlaufen wird. Der Schleifenkörper (Zeile 6) hat die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;. Nach der Verschachtelungsregel müssen wir die beiden Komplexitäten multiplizieren, und es ergibt sich:&lt;br /&gt;
&lt;br /&gt;
::&amp;lt;math&amp;gt;\mathcal{O}(k)\cdot\mathcal{O}(1) = \mathcal{O}(k\cdot 1)=\mathcal{O}(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Wir betrachten nun die äußere Schleife. Der Schleifenkopf (Zeile 4) wird (N-k)-mal durchlaufen und hat somit eine Komplexität von &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt;. Der Schleifenkörper (Zeilen 5 bis 7) besteht aus der inneren Schleife (Zeilen 5 und 6) mit der gerade berechneten Komplexität &amp;lt;math&amp;gt;\mathcal{O}(k)&amp;lt;/math&amp;gt; sowie einer einfachen Anweisung (Zeile 7) mit Komplexität &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;. Nach der Sequenzregel wird die Komplexität des Schleifenkörpers durch Addition berechnet:&lt;br /&gt;
&lt;br /&gt;
::&amp;lt;math&amp;gt;\mathcal{O}(k)+\mathcal{O}(1) = \mathcal{O}(\max(k,1)) = \mathcal{O}(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Die Komplexität der gesamten äußeren Schleife erhalten wir nach der Verschachtelungsregel wieder durch multiplizieren:&lt;br /&gt;
&lt;br /&gt;
::&amp;lt;math&amp;gt;\mathcal{O}(N)\cdot\mathcal{O}(k) = \mathcal{O}(N\cdot k)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Die übrigen Schritte des Algorithmus werden einfach nacheinander ausgeführt, so dass sie ebenfalls nach der Sequenzregel behandelt werden. Wir erhalten&lt;br /&gt;
&lt;br /&gt;
::&amp;lt;math&amp;gt;\mathcal{O}(N)+\mathcal{O}(1)+\mathcal{O}(N\cdot k)+\mathcal{O}(1) = \mathcal{O}(\max(N,1,N\cdot k,1)) = \mathcal{O}(N\cdot k)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Der gesamte Algorithmus hat also die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(N\cdot k)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
====Berechnung der Komplexität von Version 2====&lt;br /&gt;
&lt;br /&gt;
Hier gibt es nur einfache Schleifen ohne Verschachtelung. Da der Schleifenkörper jeder Schleife nur einfache Anweisungen der Komplexität &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; enthält, ergibt sich die Komplexität der Schleifen nach der Verschachtelungsregel als&lt;br /&gt;
&lt;br /&gt;
::&amp;lt;math&amp;gt;\mathcal{O}(X)\cdot\mathcal{O}(1) = \mathcal{O}(X\cdot 1)=\mathcal{O}(X)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
wobei &amp;lt;math&amp;gt;\mathcal{O}(X)&amp;lt;/math&amp;gt; die Komplexität des jeweiligen Schleifenkopfes ist. Wir erhalten also für Zeilen 4 und 5: &amp;lt;math&amp;gt;\mathcal{O}(k)&amp;lt;/math&amp;gt;, Zeilen 6 und 7: &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt;, Zeilen 8 und 9: &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt;. Die Hintereinanderausführung wird nach der Sequenzregel behandelt:&lt;br /&gt;
&lt;br /&gt;
::&amp;lt;math&amp;gt;\mathcal{O}(N)+\mathcal{O}(1)+\mathcal{O}(k)+\mathcal{O}(N)+\mathcal{O}(N)+\mathcal{O}(1) = \mathcal{O}(\max(N,1,k,N,N,1)) = \mathcal{O}(N)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Dieser Algorithmus hat also nur die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
====Fazit====&lt;br /&gt;
&lt;br /&gt;
Obwohl Version 2 mehr Schritte benötigt hat sie eine geringere Komplexität, da die for-Schleifen nicht wie bei Version 1 verschachtelt/untergeordnet sind. Bei verschachtelten for-Schleifen muss die Multiplikationsregel angewendet werden &amp;amp;rarr; höhere Komplexität.&lt;br /&gt;
&lt;br /&gt;
Die gerade berechnete Komplexität gilt aber &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; unter der Annahme, dass Array-Zugriffe konstante Komplexität &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; besitzen. Wenn dies nicht der Fall ist, kann sich die Komplexität des Algorithmus drastisch verschlechtern.&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;0&amp;quot; cellpadding=&amp;quot;5&amp;quot; &lt;br /&gt;
|Allgemein gilt:&amp;lt;br/&amp;gt;&lt;br /&gt;
Algorithmen-Analysen beruhen auf der Annahme, dass Zugriffe auf die Daten optimal schnell sind, dass heißt, dass die für den jeweiligen Algorithmus am besten geeignete Datenstruktur verwendetet wird.&amp;lt;br /&amp;gt; &amp;amp;rarr; Ansonsten: Komplexitätsverschlechterung!&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Beispiel für eine Verschlechterung der Komplexität durch Verwendung einer nicht optimalen Datenstruktur====&lt;br /&gt;
&lt;br /&gt;
Wir verwende im Mittelwert-Algorithmus eine verkettete Liste anstelle des Eingabe-Arrays a&amp;lt;/u&amp;gt;. Wir benötigen dazu eine Funktion, die das j-te Element der Liste zurückgibt. Wie üblich ist die Liste mit Hilfe einer Knotenklasse implementiert:&lt;br /&gt;
      class Node:&lt;br /&gt;
          def __init__(self, data):&lt;br /&gt;
              self.data = data&lt;br /&gt;
              self.next = None&lt;br /&gt;
&lt;br /&gt;
Die Listenklasse selbst hat ein Feld &amp;lt;tt&amp;gt;head&amp;lt;/tt&amp;gt;, das eine Referenz auf den ersten Knoten speichert, und jeder Knoten speichert im Feld &amp;lt;tt&amp;gt;next&amp;lt;/tt&amp;gt; eine Referenz auf seinen Nachfolger. Um zum j-ten Element zu gelangen, muss man die Liste sequenziell durchlaufen&lt;br /&gt;
      def get_jth(list, j):&lt;br /&gt;
           r = list.head&lt;br /&gt;
           while j &amp;gt; 0:&lt;br /&gt;
               r = r.head&lt;br /&gt;
               j -= 1&lt;br /&gt;
           return r.data&lt;br /&gt;
Die Komplexität dieser Funktion ist offensichtlich &amp;lt;math&amp;gt;\mathcal{O}(j)&amp;lt;/math&amp;gt; (Komplexitätsberechnung wie oben). Wir setzen jetzt bei Version 1 des Mittelwert-Algorithmus diese Funktion in Zeile 6 anstelle des Indexzugriffs &amp;lt;tt&amp;gt;a[i]&amp;lt;/tt&amp;gt; ein (nur in dieser Zeile wird auf die Elemente des Arrays zugegriffen). Wir erhalten folgende Implementation (die Änderungen sind rot markiert):&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;0&amp;quot; cellpadding=&amp;quot;2&amp;quot; &lt;br /&gt;
|-&lt;br /&gt;
! Programmzeile&lt;br /&gt;
! Version 1 mit Liste: O(N * k)&lt;br /&gt;
! Komplexit&amp;amp;auml;t&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
1.&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;tt&amp;gt;r = [0] * len(a)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(N)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
2.&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;tt&amp;gt;if k &amp;gt; len(a):&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(1)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
3.&lt;br /&gt;
|&lt;br /&gt;
:: &amp;lt;tt&amp;gt;raise RuntimeError (&amp;quot;k zu gro&amp;amp;szlig;&amp;quot;)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
4.&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;tt&amp;gt;for j in range(k-1, len(a)):&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;center&amp;gt;O(N-k+1) = '''O(N)'''&amp;lt;/center&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
5.&lt;br /&gt;
|&lt;br /&gt;
:: &amp;lt;tt&amp;gt;for i in range(j-k+1, j+1):&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(k)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
6.&lt;br /&gt;
|&lt;br /&gt;
:::: &amp;lt;tt&amp;gt;r[j] += &amp;lt;font color=red&amp;gt;get_jth(a, i)&amp;lt;/font&amp;gt;&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;&amp;lt;font color=red&amp;gt;O(i)&amp;lt;/font&amp;gt;&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
7.&lt;br /&gt;
|&lt;br /&gt;
:: &amp;lt;tt&amp;gt;r[j] /= float(k)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(1)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
8.&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;tt&amp;gt;return r&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(1)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Der Aufruf der Funktion &amp;lt;tt&amp;gt;get_jth&amp;lt;/tt&amp;gt; ist jetzt gleichbedeutend mit einer dreifach verschachtelten Schleife (weil &amp;lt;tt&amp;gt;get_jth&amp;lt;/tt&amp;gt; ja eine zusatzliche Schleife enthält). Die Anzahl der Operationen in Zeile 4 bis 6 ist jetzt&lt;br /&gt;
&lt;br /&gt;
::&amp;lt;math&amp;gt;f(N,k)=\sum_{j=k-1}^{N-1}\,\sum_{i=j-k+1}^j\,\mathcal{O}(i)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
wobei das &amp;lt;math&amp;gt;\mathcal{O}(i)&amp;lt;/math&amp;gt; die neue Schleife durch Verwendung der Liste repräsentiert. Mit Mathematica-Hilfe [http://www.wolfram.com/] lässt sich diese Summe exakt ausrechnen&lt;br /&gt;
&lt;br /&gt;
::&amp;lt;math&amp;gt;f(N,k)=\frac{1}{2}(k N^2-k^2 N+k^2-k)\in \mathcal{O}(k N^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Die Komplexitätsberechnung erfolgte dabei nach der Regel für Polynome unter Beachtung von &amp;lt;math&amp;gt;k \ll N&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
====Fazit:====&lt;br /&gt;
&lt;br /&gt;
Die Komplexität von Version 1 mit einer verketteten Liste wäre O(N&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; * k)&lt;br /&gt;
'''&amp;amp;rarr; Die richtige Datenstruktur ist wichtig, da es sonst zu einer Komplexitätsverschlechterung kommen kann!'''&lt;br /&gt;
&lt;br /&gt;
Auf Version 2 unseres Running Average-Beispiels hätte eine verkettete Liste allerdings keine Auswirkungen, da die inkrementelle Berechnung der Summen in Zeile 7 weiterhin möglich ist (bei geschickter Implementation!) und somit Version 2 immer noch eine Komplexität von O(N) hätte.&lt;br /&gt;
&lt;br /&gt;
==Amortisierte Komplexität==&lt;br /&gt;
&lt;br /&gt;
Bis jetzt wurde die Komplexität nur im schlechtesten Fall (Worst Case) betrachtet. Bei einigen Algorithmen schwankt die Komplexität im schlechtesten Fall jedoch, wenn man die ungünstige Operation mehrmals hintereinander ausführt. Die amortisierte Komplexität beschäftigt sich mit der durchschnittlichen Komplexität über viele Aufrufe der ungünstigsten Operation.&lt;br /&gt;
&lt;br /&gt;
Zum weiter Lesen: [[http://de.wikipedia.org/wiki/Amortisierte_Laufzeitanalyse Wikipedia: Amortisierte Laufzeitanalyse]]&lt;br /&gt;
&lt;br /&gt;
===Beispiel: Inkrementieren von Binärzahlen===&lt;br /&gt;
&lt;br /&gt;
Frage: Angenommen, das Umdrehen eines Bits einer Binärzahl verursacht Kosten von 1 Einheit. Wir erzeugen die Folge der natürlichen Zahlen durch sukzessives Inkrementieren, von Null beginnend. Bei jeder Inkrementierung werden einige Bits verändert, aber diese Zahl (und damit die Kosten der Inkrementierungen) ''schwanken'' sehr stark. Wir fragen jetzt, was eine Inkrementierung im Durchschnitt kostet?&lt;br /&gt;
&lt;br /&gt;
Um diese Durchschnittskosten zu berechnen, bezahlen wir bei jeder Inkrementierung 2 Einheiten. Wenn davon nach Abzug der Kosten der jeweiligen Operation noch etwas übrig bleibt, wird der Rest dem  Guthaben zugeschrieben. Umgekehrt wird ein eventueller Fehlbetrag (wenn eine Inkrementierung mehr als 2 Bits umdreht) aus dem Guthaben gedeckt. Dadurch werden die ansonsten großen Schwankungen der Kosten ausgeglichen:&lt;br /&gt;
:: Kosten &amp;lt; Einzahlung &amp;amp;rarr; es wird gespart&lt;br /&gt;
:: Kosten = Einzahlung &amp;amp;rarr; Guthaben bleibt unverändert&lt;br /&gt;
:: Kosten &amp;gt; Einzahlung &amp;amp;rarr; Guthaben wird für die Kosten verbraucht&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;0&amp;quot; cellpadding=&amp;quot;5&amp;quot; &lt;br /&gt;
!Schritte&lt;br /&gt;
!Zahlen&lt;br /&gt;
!Kosten &amp;lt;br/&amp;gt;&lt;br /&gt;
(Anzahl der geänderten Bits)&lt;br /&gt;
! Einzahlung&lt;br /&gt;
!Guthaben =&amp;lt;br/&amp;gt;&lt;br /&gt;
altes Guthaben + Einzahlung - Kosten&lt;br /&gt;
|-&lt;br /&gt;
|1.&lt;br /&gt;
|0000&amp;lt;u&amp;gt;&amp;lt;span style=&amp;quot;color:#00BFFF;&amp;quot;&amp;gt;1&amp;lt;/span&amp;gt;&amp;lt;/u&amp;gt;&lt;br /&gt;
|1&lt;br /&gt;
|'''2'''&lt;br /&gt;
|'''1'''&lt;br /&gt;
|-&lt;br /&gt;
|2.&lt;br /&gt;
|000&amp;lt;u&amp;gt;&amp;lt;span style=&amp;quot;color:#00BFFF;&amp;quot;&amp;gt;1&amp;lt;/span&amp;gt;&amp;lt;/u&amp;gt;&amp;lt;u&amp;gt;&amp;lt;span style=&amp;quot;color:#00BFFF;&amp;quot;&amp;gt;0&amp;lt;/span&amp;gt;&amp;lt;/u&amp;gt;&lt;br /&gt;
|2&lt;br /&gt;
|'''2'''&lt;br /&gt;
|'''1'''&lt;br /&gt;
|-&lt;br /&gt;
|3.&lt;br /&gt;
|0001&amp;lt;u&amp;gt;&amp;lt;span style=&amp;quot;color:#00BFFF;&amp;quot;&amp;gt;1&amp;lt;/span&amp;gt;&amp;lt;/u&amp;gt;&lt;br /&gt;
|1&lt;br /&gt;
|'''2'''&lt;br /&gt;
|'''2'''&lt;br /&gt;
|-&lt;br /&gt;
|4.&lt;br /&gt;
|00&amp;lt;u&amp;gt;&amp;lt;span style=&amp;quot;color:#00BFFF;&amp;quot;&amp;gt;1&amp;lt;/span&amp;gt;&amp;lt;/u&amp;gt;&amp;lt;u&amp;gt;&amp;lt;span style=&amp;quot;color:#00BFFF;&amp;quot;&amp;gt;0&amp;lt;/span&amp;gt;&amp;lt;/u&amp;gt;&amp;lt;u&amp;gt;&amp;lt;span style=&amp;quot;color:#00BFFF;&amp;quot;&amp;gt;0&amp;lt;/span&amp;gt;&amp;lt;/u&amp;gt;&lt;br /&gt;
|3&lt;br /&gt;
|'''2'''&lt;br /&gt;
|'''1'''&lt;br /&gt;
|-&lt;br /&gt;
|5.&lt;br /&gt;
|0010&amp;lt;u&amp;gt;&amp;lt;span style=&amp;quot;color:#00BFFF;&amp;quot;&amp;gt;1&amp;lt;/span&amp;gt;&amp;lt;/u&amp;gt;&lt;br /&gt;
|1&lt;br /&gt;
|'''2'''&lt;br /&gt;
|'''2'''&lt;br /&gt;
|-&lt;br /&gt;
|6.&lt;br /&gt;
|001&amp;lt;u&amp;gt;&amp;lt;span style=&amp;quot;color:#00BFFF;&amp;quot;&amp;gt;10&amp;lt;/span&amp;gt;&amp;lt;/u&amp;gt;&lt;br /&gt;
|2&lt;br /&gt;
|'''2'''&lt;br /&gt;
|'''2'''&lt;br /&gt;
|-&lt;br /&gt;
|7.&lt;br /&gt;
|0011&amp;lt;u&amp;gt;&amp;lt;span style=&amp;quot;color:#00BFFF;&amp;quot;&amp;gt;1&amp;lt;/span&amp;gt;&amp;lt;/u&amp;gt;&lt;br /&gt;
|1&lt;br /&gt;
|'''2'''&lt;br /&gt;
|'''3'''&lt;br /&gt;
|-&lt;br /&gt;
|8.&lt;br /&gt;
|0&amp;lt;u&amp;gt;&amp;lt;span style=&amp;quot;color:#00BFFF;&amp;quot;&amp;gt;1000&amp;lt;/span&amp;gt;&amp;lt;/u&amp;gt;&lt;br /&gt;
|4&lt;br /&gt;
|'''2'''&lt;br /&gt;
|'''1'''&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Die Kosten ergeben sich aus der Anzahl der Ziffern die von 1 nach 0, bzw. von 0 nach 1 verändert werden&lt;br /&gt;
&lt;br /&gt;
&amp;lt;u&amp;gt;Rechnung:&amp;lt;/u&amp;gt;&lt;br /&gt;
&lt;br /&gt;
1. Schritt: Kosten: 1 &amp;lt; Einzahlung: 2&amp;lt;br /&amp;gt;&lt;br /&gt;
:: &amp;amp;rarr; es wird gespart&amp;lt;br /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. Schritt: Kosten: 2 = Einzahlung: 2&amp;lt;br /&amp;gt;&lt;br /&gt;
:: &amp;amp;rarr; es wird nicht gespart&amp;lt;br /&amp;gt;&lt;br /&gt;
:: &amp;amp;rarr; Guthaben bleibt so wie es ist &amp;lt;br /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. Schritt: Kosten: 1 &amp;lt; Einzahlung: 2&amp;lt;br /&amp;gt;&lt;br /&gt;
:: &amp;amp;rarr; es wird gespart&amp;lt;br /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
4. Schritt: Kosten: 3 &amp;gt; Einzahlung: 2&amp;lt;br /&amp;gt;&lt;br /&gt;
:: &amp;amp;rarr; es wird eine 1 vom Guthaben genommen um die Kosten zu zahlen&amp;lt;br /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
usw.&lt;br /&gt;
&lt;br /&gt;
Man erkennt, dass vor teuren Operation (Wechsel von 3 auf 4 bzw. von 7 auf 8) genügend Guthaben angespart wurde, um die Kosten zu decken. Das Guthaben geht bei diesen Operationen immer wieder auf 1 zurück, aber es wird nie vollständig verbraucht (Dies kann natürlich auch mathematisch exakt bewiesen werden, wie wir es unten am Beispiel des dynamische Arrays zeigen). Wir schließen daraus, dass die durchschnittlichen oder '''amortisierten Kosten''' einer Inkrementierungsoperation gleich 2 sind.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Zum weiter Lesen: [[http://de.wikipedia.org/wiki/Account-Methode Wikipedia Account-Methode]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
====Fazit====&lt;br /&gt;
Die amortisierte Komplexität beschäftigt sich mit dem Durchschnitt aller Operation im ungünstigsten Fall. Operationen mit hohen Kosten, die aber nur selten ausgeführt werden, fallen bei der amortisierten Komplexität nicht so ins Gewicht. Bei Algorithmen, die gelegentlich eine &amp;quot;teure&amp;quot; Operation benutzen, ansonsten jedoch &amp;quot;billigen&amp;quot; Operationen aufrufen, kann die amortisierte Komplexität niedriger sein als die Komplexität im schlechtesten (Einzel-)Fall.&lt;br /&gt;
&lt;br /&gt;
In unserem Beispiel fallen die teuren Einzelschritte (z.B. 4. und 8. Schritt) bei den amortisierten Kosten nicht so ins Gewicht, da wir die Kosten aus unserem Guthaben mitbezahlen können. Das Guthaben ist immer groß genug, weil jeder zweite Aufruf eine billige Operation ist, die nur ein Bit umdreht und somit das Ansparen ermöglichen. Diese Betrachtung zeigt, dass die amortisierte (d.h. durchschnittliche) Komplexität des Algoithmus niedriger (nämlich konstant) ist als die Komplexität im schlechtesten Fall.&lt;br /&gt;
&lt;br /&gt;
===statisches Array===&lt;br /&gt;
&lt;br /&gt;
Ein statisches Array hat eine feste Größe N und das Erweitern des Arrays um ein neues Element besitzt eine Komplexität von O(N), &lt;br /&gt;
denn es muss ein neues Array mit der Größe N+1 erzeugt werden.&amp;lt;br /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;u&amp;gt;Anhängen eines weiteren Elements an ein statisches Array:&amp;lt;/u&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;0&amp;quot; cellpadding=&amp;quot;5&amp;quot; align=&amp;quot;right&amp;quot;&lt;br /&gt;
!Schritte&lt;br /&gt;
|'''Array'''&lt;br /&gt;
&amp;lt;small&amp;gt;(wie es aussehen könnte)&amp;lt;/small&amp;gt;&lt;br /&gt;
!Komplexität&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;center&amp;gt;altes Array&amp;lt;/center&amp;gt;&lt;br /&gt;
|&amp;lt;center&amp;gt;[0,1,2,3]&amp;lt;/center&amp;gt;&lt;br /&gt;
|&amp;lt;center&amp;gt;-&amp;lt;/center&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|1. Array N+1&lt;br /&gt;
|&amp;lt;center&amp;gt;[None,None,None,None,None]&amp;lt;/center&amp;gt;&lt;br /&gt;
|&amp;lt;center&amp;gt;O(N+1) = '''O(N)'''&amp;lt;/center&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|2. Kopieren&lt;br /&gt;
|&amp;lt;center&amp;gt;[0,1,2,3,None]&amp;lt;/center&amp;gt;&lt;br /&gt;
|&amp;lt;center&amp;gt;'''O(N)'''&amp;lt;/center&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|3. append von &amp;quot;x&amp;quot;&lt;br /&gt;
|&amp;lt;center&amp;gt;[0,1,2,3,'x']&amp;lt;/center&amp;gt;&lt;br /&gt;
|&amp;lt;center&amp;gt;'''O(1)'''&amp;lt;/center&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
altesArray = [0,1,2,3]&amp;lt;br /&amp;gt;&lt;br /&gt;
altesArray.append('x')&lt;br /&gt;
&lt;br /&gt;
1. Es wird ein neues Array der Größe N+1 erzeugt&amp;lt;br /&amp;gt;&lt;br /&gt;
2. Die Daten aus dem alten Array werden in das neue Array mit der Länge N+1 kopiert&amp;lt;small&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
(Die Operation besitzt nur eine Komplexität von O(N), wenn  das Kopieren eines Elements eine Komplexität von O(1) besitzt)&amp;lt;/small&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
3. 'x' wird an die letzte Stelle des neuen Arrays geschrieben&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;u&amp;gt;Additionsregel:&amp;lt;/u&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
O(N) + O(N) + O(1) &amp;amp;isin; O(N), falls O(1) &amp;amp;isin; O(N) [O(max(O(N),O(1))] &amp;lt;small&amp;gt;(Bedingung: N &amp;gt; 1)&amp;lt;/small&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===dynamisches Array===&lt;br /&gt;
&lt;br /&gt;
Beim dynamischen Array werden mehr Speicherelemente reserviert als zur Zeit benötigt. Wir unterscheiden deshalb &lt;br /&gt;
&lt;br /&gt;
::&amp;lt;tt&amp;gt;capacity&amp;lt;/tt&amp;gt; = Anzahl der möglichen Elemente, die in das Array passen&amp;lt;br /&amp;gt;&lt;br /&gt;
::&amp;lt;tt&amp;gt;size&amp;lt;/tt&amp;gt; = Anzahl der Elemente, die im Array zur Zeit gespeichert sind&amp;lt;br /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Die Daten selbst werden in einem statischen Array gespeichert:&lt;br /&gt;
::&amp;lt;tt&amp;gt;data&amp;lt;/tt&amp;gt; = statisches Array der Größe &amp;quot;capacity&amp;quot;&amp;lt;br /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ist dieses Array voll, wird ein neues Datenarray der doppelten Größe allokiert, und die Daten werden vom alten ins neue Array kopiert. Die Vorgehensweise &lt;br /&gt;
beim Zufügen eines neuen Elements im Fall (size == capacity) ist also&lt;br /&gt;
* capacity wird verdoppelt&amp;lt;br /&amp;gt;&lt;br /&gt;
: neue capacity = 2 * alte capacity&lt;br /&gt;
: (allgemein genügt es auch, wenn capacity wird um einen bestimmten Prozentsatz vergrößert wird, &lt;br /&gt;
:: neue capacity = alte capacity * c (mit c &amp;gt; 1, z.B. c = 1.2)&lt;br /&gt;
* ein neues statisches Array der Größe 'neue capacity' wird erzeugt&lt;br /&gt;
* das alte Array wird ins neue kopiert und danach freigegeben&lt;br /&gt;
* das anzufügende Element wird ins neue Array eingefügt&lt;br /&gt;
&lt;br /&gt;
'''Folge:''' Die Kosten für das Vergrößern der Kapazität werden amortisiert über viele Einfügungen, die kein Vergrößern erfordern. Die Operation &amp;lt;tt&amp;gt;append&amp;lt;/tt&amp;gt; besitzt amortisierte Komplexität  O(1), wie wir jetzt zeigen:&lt;br /&gt;
&lt;br /&gt;
===Analyse des dynamischen Arrays===&lt;br /&gt;
&lt;br /&gt;
Durchschnitt der Gesamtkosten für N-maliges append = &amp;lt;math&amp;gt;\frac{1}{N} \sum_{i = 1}^N Kosten(i)&amp;lt;/math&amp;gt;. Zur Analyse der amortisierten Komplexität wird ein Potential&amp;lt;br/&amp;gt;&lt;br /&gt;
::&amp;amp;Phi;&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; = 2*size&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; - capacity&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
eingeführt, wobei das Array nach dem i-ten Einfüge-Schritt die Größe size&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; und die Kapizität capacity&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; hat. Wir nehmen vereinfachend an, dass es keine Löschoperationen gibt. Dann gilt nach dem i-ten Schritt jeweils&lt;br /&gt;
::&amp;amp;Phi;&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; = 2*i - capacity&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;u&amp;gt;Fall 1: Array ist nicht voll&amp;lt;/u&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
Es wird kein Umkopieren benötigt, da das Array noch nicht voll ist&amp;lt;br/&amp;gt; &lt;br /&gt;
&amp;amp;rarr; size&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt; &amp;lt; capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt;&amp;lt;br/&amp;gt; &lt;br /&gt;
&amp;amp;rarr; capacity&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; == capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Kosten: 1 (für Einfügen des neuen Elements)&amp;lt;br /&amp;gt;&lt;br /&gt;
Potenzial vor append: &amp;amp;Phi;&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt; = 2(i - 1) - capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
Potenzial nach append: &amp;amp;Phi;&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; = 2i - capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
amortisierte Kosten = Kosten&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; + &amp;amp;Phi;&amp;lt;sub&amp;gt;(i)&amp;lt;/sub&amp;gt; - &amp;amp;Phi;&amp;lt;sub&amp;gt;(i-1)&amp;lt;/sub&amp;gt;&lt;br /&gt;
:::::                 = 1            + (2i - capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt;)        - [2(i - 1) - capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt;]&lt;br /&gt;
:::::                 = 1            + 2i - capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt;         - 2i + 2 + capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt;&lt;br /&gt;
:::::                 = 1            + &amp;lt;del&amp;gt;2i&amp;lt;/del&amp;gt; - &amp;lt;del&amp;gt;capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt;&amp;lt;/del&amp;gt; - &amp;lt;del&amp;gt;2i&amp;lt;/del&amp;gt; + 2 + &amp;lt;del&amp;gt;capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt;&amp;lt;/del&amp;gt;&lt;br /&gt;
:::::                 = 1 + 2&lt;br /&gt;
:::::                 = 3 = O(1) &amp;amp;rarr; konstant&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;u&amp;gt;Fall 2: Array ist voll&amp;lt;/u&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
Vor dem i-ten append muss umkopiert werden&amp;lt;br/&amp;gt; &lt;br /&gt;
&amp;amp;rarr; size&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt; == capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt; == i-1&amp;lt;br/&amp;gt; &lt;br /&gt;
&amp;amp;rarr; Allokieren eines neuen statischen Arrays mit verdoppelter Kapazität notwendig, also capacity&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; == 2*capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Kosten: (i-1) + 1 (für Umkopieren und Einfügen des neuen Elements)&amp;lt;br /&amp;gt;&lt;br /&gt;
Potenzial vor append =  &amp;amp;Phi;&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt; = 2(i - 1) - capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
Potenzial nach append =  &amp;amp;Phi;&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; =  2i - 2 capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
amortisierte Kosten = Kosten&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; + &amp;amp;Phi;&amp;lt;sub&amp;gt;(i)&amp;lt;/sub&amp;gt; - &amp;amp;Phi;&amp;lt;sub&amp;gt;(i-1)&amp;lt;/sub&amp;gt;&lt;br /&gt;
:::::               = ((i - 1) + 1) + 2i - 2 capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt; - [2(i-1) - capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt;]&lt;br /&gt;
:::::               = i + &amp;lt;del&amp;gt;2i&amp;lt;/del&amp;gt; - 2 capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt; - &amp;lt;del&amp;gt;2i&amp;lt;/del&amp;gt; + 2 - capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt;&lt;br /&gt;
:::::               = i + 2 - capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt;&lt;br /&gt;
:::::               = i + 2 - (i - 1) &amp;lt;small&amp;gt;(da capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt; = i-1)&amp;lt;/small&amp;gt;&lt;br /&gt;
:::::               = 3 = O(1) &amp;amp;rarr; konstant            &lt;br /&gt;
&lt;br /&gt;
'''Damit wurde bewiesen, dass die Operation &amp;lt;tt&amp;gt;append&amp;lt;/tt&amp;gt; beim dynamischen Array eine amortisierte Komplexität von 3 Einheiten hat, also &amp;lt;tt&amp;gt;append&amp;lt;/tt&amp;gt; &amp;amp;isin; O(1)'''. Diese Operation kann deshalb gefahrlos in der inneren Schleife eines Algorithmus benutzt werden.&lt;br /&gt;
&lt;br /&gt;
==== Beispiel für 9 Einfügeoperationen ====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;0&amp;quot; cellpadding=&amp;quot;5&amp;quot;&lt;br /&gt;
!Array&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;small&amp;gt;(wie es aussehen könnte)&amp;lt;/small&amp;gt;&lt;br /&gt;
!size&lt;br /&gt;
!capacity&lt;br /&gt;
!Kosten für append&amp;lt;br /&amp;gt;(einschließlich Umkopieren)&lt;br /&gt;
!Summe Kosten&lt;br /&gt;
!Durchschnittskosten&lt;br /&gt;
!&amp;amp;Phi;&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; = 2 * size - capacity&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;small&amp;gt;(i = size)&amp;lt;/small&amp;gt;&lt;br /&gt;
!Potenzialdifferenz&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;amp;Delta; &amp;amp;Phi;&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; = &amp;amp;Phi;&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; - &amp;amp;Phi;&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt;&lt;br /&gt;
!amortisierte Kosten&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
= Kosten&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; + &amp;amp;Delta; &amp;amp;Phi;&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;center&amp;gt;[None]&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;0&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;1&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;-&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;-&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;-&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;-1&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;-&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;-&amp;lt;/center&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;center&amp;gt;[a]&amp;lt;/center&amp;gt;&amp;lt;center&amp;gt;&amp;lt;span style=&amp;quot;color:#00BFFF;&amp;quot;&amp;gt;Array ist voll!&amp;lt;/span&amp;gt;&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;1&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;1&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;1&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;1&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;1&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;1&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;2&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;3&amp;lt;/center&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;center&amp;gt;[a,b]&amp;lt;/center&amp;gt;&amp;lt;center&amp;gt;&amp;lt;span style=&amp;quot;color:#00BFFF;&amp;quot;&amp;gt;Array ist voll!&amp;lt;/span&amp;gt;&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;2&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;2&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;1 + 1&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;3&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;3/2&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;2&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;1&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;3&amp;lt;/center&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;center&amp;gt;[a,b,c,None]&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;3&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;4&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;2 + 1&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;6&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;6/3&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;2&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;0&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;3&amp;lt;/center&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;center&amp;gt;[a,b,c,d]&amp;lt;/center&amp;gt;&amp;lt;center&amp;gt;&amp;lt;span style=&amp;quot;color:#00BFFF;&amp;quot;&amp;gt;Array ist voll!&amp;lt;/span&amp;gt;&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;4&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;4&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;1&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;7&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;7/4&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;4&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;2&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;3&amp;lt;/center&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;center&amp;gt;[a,b,c,d,e,None,None,None]&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;5&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;8&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;4 + 1&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;12&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;12/5&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;2&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;-2&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;3&amp;lt;/center&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;center&amp;gt;[a,b,c,d,e,f,None,None]&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;6&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;8&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;1&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;13&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;13/6&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;4&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;2&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;3&amp;lt;/center&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;center&amp;gt;[a,b,c,d,e,f,g,None]&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;7&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;8&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;1&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;14&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;14/7&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;6&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;2&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;3&amp;lt;/center&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;center&amp;gt;[a,b,c,d,e,f,g,h]&amp;lt;/center&amp;gt;&amp;lt;center&amp;gt;&amp;lt;span style=&amp;quot;color:#00BFFF;&amp;quot;&amp;gt;Array ist voll!&amp;lt;/span&amp;gt;&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;8&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;8&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;1&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;15&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;15/8&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;8&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;2&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;3&amp;lt;/center&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;center&amp;gt;[a,b,c,d,e,f,g,h,j,None,None,None,&amp;lt;br /&amp;gt;&lt;br /&gt;
None,None,None,None]&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;9&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;16&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;8 + 1&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;24&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;24/9&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;2&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;-6&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;3&amp;lt;/center&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Die durchschnittlichen Kosten betragen stets etwa 2 Einheiten, schwanken allerdings so, dass nicht unmittelbar ersichtlich ist, ob dies für sämtliche Einfügeoperationen gilt. Die amortisierte Komplexität, die mit Hilfe des Potentials berechnet wird, ist hingegen konstant 3, wie auch im obigen Beweis für alle Einfügeoperationen allgemein gezeigt wurde.&lt;br /&gt;
&lt;br /&gt;
[[Suchen|Nächstes Thema]]&lt;/div&gt;</summary>
		<author><name>Jschleic</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Effizienz&amp;diff=4728</id>
		<title>Effizienz</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Effizienz&amp;diff=4728"/>
				<updated>2010-08-10T12:18:32Z</updated>
		
		<summary type="html">&lt;p&gt;Jschleic: /* Optimierung der Laufzeit */ quadrat. Gleichung&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Bei der Diskussion von Effizienz müssen wir zwischen der Laufzeit eines Algorithmus auf einem bestimmten System und seiner prinzipiellen Leistungsfähigkeit (Algorithmenkomplexität) unterscheiden. Der Benutzer ist natürlich vor allem an der Laufzeit interessiert, denn diese bestimmt letztendlich seine Arbeitsproduktivität. Ein Softwaredesigner hingegen muss eine Implementation wählen, die auf verschiedenen Systemen und in verschiedenen Anwendungen schnell ist. Für ihn sind daher auch Aussagen zur Algorithmenkomplexität sehr wichtig, um den am besten geeigneten Algorithmus auszuwählen.&lt;br /&gt;
&lt;br /&gt;
== Laufzeit ==&lt;br /&gt;
&lt;br /&gt;
Aus Anwendersicht ist ein Algorithmus effizient, wenn er die in der Spezifikation verlangten Laufzeitgrenzen einhält. Ein Algorithmus muss also nicht immer so schnell wie möglich sein, sondern so schnell wie nötig. Dies führt in verschiedenen Anwendungen zu ganz unterschiedliche Laufzeitanforderungen:&lt;br /&gt;
&lt;br /&gt;
* Berechnen des nächsten Steuerkommandos für eine Maschine: ca. 1/1000s&lt;br /&gt;
* Berechnen des nächsten Bildes für eine Videopräsentation (z.B. Dekompression von MPEG-kodierten Bildern): ca. 1/25s&lt;br /&gt;
: Geringere Bildraten führen zu ruckeligen Filmen.&lt;br /&gt;
* Sichtbare Antwort auf ein interaktives Kommando (z.B. Mausklick): ca. 1/2s&lt;br /&gt;
: Wird diese Antwortzeit überschritten, vermuten viele Benutzer, dass der Mausklick nicht funktioniert hat, und klicken nochmals, mit eventuell fatalen Folgen. Wenn ein Algorithmus notwendigerweise länger dauert als 1/2s, sollte ein Fortschrittsbalken angezeigt werden.&lt;br /&gt;
* Wettervorhersage: muss spätestens am Vorabend des vorhergesagten Tages beendet sein&lt;br /&gt;
&lt;br /&gt;
===Laufzeitvergleich===&lt;br /&gt;
&lt;br /&gt;
Da die Laufzeit für den Benutzer ein so wichtiges Kriterium ist, werden häufig Laufzeitvergleiche durchgeführt. Deren Ergebnisse hängen allerdings von vielen Faktoren ab, die möglicherweise nicht kontrollierbar sind:&lt;br /&gt;
* Geschwindigkeit und Anzahl der Prozessoren&lt;br /&gt;
* Auslastung des Systems&lt;br /&gt;
* Größe des Hauptspeichers  und Cache, Geschwindigkeit des  Datenbus&lt;br /&gt;
* Qualität des Compilers/Optimierers (ist der Compiler für die spezielle Prozessor-Architektur optimiert?)&lt;br /&gt;
* Geschick des Programmierers&lt;br /&gt;
* Daten (Beispiel Quicksort: Best case und worst case [vorsortierter Input] stark unterschiedlich)&lt;br /&gt;
All diese Faktoren sind untereinander abhängig. Laufzeitvergleiche sind daher mit Vorsicht zu interpretieren.&lt;br /&gt;
Generell sollten bei Vergleichen möglichst wenige Parameter verändert werden, z.B.&lt;br /&gt;
* gleiches Programm (gleiche Kompilierung), gleiche Daten, andere Prozessoren&lt;br /&gt;
oder&lt;br /&gt;
* gleiche CPU, Daten, andere Programme (Vergleich von Algorithmen)&lt;br /&gt;
Zur Verbesserung der Vergleichbarkeit gibt es standardisierte [http://en.wikipedia.org/wiki/Benchmark_(computing) Benchmarks], die bestimmte Aspekte eines Systems unter möglichst realitätsnahen Bedingungen testen. Generell gilt aber: Durch Laufzeitmessung ist schwer festzustellen, ob ein Algorithmus ''prinzipiell'' besser ist als ein anderer. Dafür ist die Analyse der [[Effizienz#Algorithmen-Komplexität|Algorithmenkomplexität]] notwendig.&lt;br /&gt;
&lt;br /&gt;
===Optimierung der Laufzeit===&lt;br /&gt;
&lt;br /&gt;
Wenn sich herausstellt, dass ein bereits implementierter Algorithmus zu langsam läuft, geht man wie folgt vor:&lt;br /&gt;
&lt;br /&gt;
# Man verwendet einen [http://en.wikipedia.org/wiki/Performance_analysis Profiler], um zunächst den Flaschenhals zu bestimmen. Ein Profiler ist ein Hilfsprogramm, das während der Ausführung eines Programms misst, wieviel Zeit in jeder Funktion und Unterfunktion verbraucht wird. Dadurch kann man herausfinden, welcher Teil des Algorithmus überhaupt Probleme bereitet. Donald Knuth gibt z.B. als Erfahrungswert an, dass Programme während des größten Teils ihrer Laufzeit nur 3% des Quellcodes (natürlich mehrmals wiederholt) ausführen [http://pplab.snu.ac.kr/courses/adv_pl05/papers/p261-knuth.pdf]. Es ist sehr wichtig, diese 3% experimentell zu bestimmen, weil die Erfahrung zeigt, dass man beim Erraten der kritischen Programmteile oft falsch liegt. Man spricht dann von &amp;quot;[http://en.wikipedia.org/wiki/Optimization_%28computer_science%29#When_to_optimize premature optimization]&amp;quot;, also von voreiliger Optimierung ohne experimentelle Untersuchung der wirklichen Laufzeiten, was laut Knuth &amp;quot;the root of all evil&amp;quot; ist. Der Python-Profiler wird in [http://docs.python.org/lib/profile.html Kapitel 25] der Python-Dokumentation beschrieben.&lt;br /&gt;
# Man kann dann versuchen, die kritischen Programmteile zu optimieren.&lt;br /&gt;
# Falls der Laufzeitgewinn durch Optimierung zu gering ist, muss man einen prinzipiell schnelleren Algorithmus verwenden, falls es einen gibt.&lt;br /&gt;
&lt;br /&gt;
Einige wichtige Techniken der Programmoptimierung sollen hier erwähnt werden. Wenn man einen optimierenden Compiler verwendet, werden einige Optimierungen automatisch ausgeführt [http://en.wikipedia.org/wiki/Compiler_optimization]. In Python trifft dies jedoch nicht zu. Um den Sinn einiger Optimierungen zu verstehen, benötigt man Grundkenntnisse der Computerarchitektur.&lt;br /&gt;
&lt;br /&gt;
;Elimination von redundantem Code: Es ist offensichtlich überflüssig, dasselbe Ergebnis mehrmals zu berechnen, wenn es auch zwischengespeichert werden könnte. Diese Optimierung wird von vielen automatischen Optimierern unterstützt und kommt im wesentlichen in zwei Ausprägungen vor:&lt;br /&gt;
:; common subexpression elimination: In mathematischen Ausdrücken wird ein Teilergebnis häufig mehrmals benötigt. Man betrachte z.B. die Lösung der quadratischen Gleichung &amp;lt;math&amp;gt;x^2+p\,x+q = 0&amp;lt;/math&amp;gt;:&lt;br /&gt;
        x1 = - p / 2.0 + sqrt(p*p/4.0 - q)&lt;br /&gt;
        x2 = - p / 2.0 - sqrt(p*p/4.0 - q)&lt;br /&gt;
::Die mehrmalige Berechnung von Teilausdrücken wird vermieden, wenn man stattdessen schreibt:&lt;br /&gt;
        p2 = - p / 2.0&lt;br /&gt;
        r  = sqrt(p2*p2 - q)&lt;br /&gt;
        x1 = p2 + r&lt;br /&gt;
        x2 = p2 - r&lt;br /&gt;
:; loop invariant elimination: Wenn ein Teilausdruck sich in einer Schleife nicht ändert, muss man ihn nicht bei jedem Schleifendurchlauf neu berechnen, sondern kann dies einmal vor Beginn der Schleife tun. Ein typisches Beispiel hierfür ist die Adressierung von Matrizen, die als 1-dimensionales Array gespeichert sind. Angenommen, wir speichern eine NxN Matrix &amp;lt;tt&amp;gt;m&amp;lt;/tt&amp;gt; in einem Array &amp;lt;tt&amp;gt;a&amp;lt;/tt&amp;gt; der Größe N&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, so dass das Matrixelement &amp;lt;tt&amp;gt;m&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt;&amp;lt;/tt&amp;gt; durch &amp;lt;tt&amp;gt;a[i + j*N]&amp;lt;/tt&amp;gt; indexiert wird. Wir betrachten die Aufgabe, eine Einheitsmatrix zu initialisieren. Ein nicht optimierter Algorithmus dafür lautet:&lt;br /&gt;
       for j in range(N):&lt;br /&gt;
           for i in range(N):&lt;br /&gt;
               if i == j:&lt;br /&gt;
                    a[i + j*N] = 1.0&lt;br /&gt;
               else:&lt;br /&gt;
                    a[i + j*N] = 0.0&lt;br /&gt;
::Der Ausdruck &amp;lt;tt&amp;gt;j*N&amp;lt;/tt&amp;gt; wird hier in jedem Schleifendurchlauf erneut berechnet, obwohl sich &amp;lt;tt&amp;gt;j&amp;lt;/tt&amp;gt; in der inneren Schleife gar nicht verändert. Man kann deshalb optimieren zu:&lt;br /&gt;
       for j in range(N):&lt;br /&gt;
           jN = j*N&lt;br /&gt;
           for i in range(N):&lt;br /&gt;
               if i == j:&lt;br /&gt;
                    a[i + jN] = 1.0&lt;br /&gt;
               else:&lt;br /&gt;
                    a[i + jN] = 0.0&lt;br /&gt;
;Vereinfachung der inneren Schleife: Generell sollte man sich bei der Optimierung auf die innere Schleife eines Algorithmus konzentrieren, weil dieser Code am häufigsten ausgeführt wird. Insbesondere sollte man die Anzahl der Befehle in der inneren Schleife so gering wie möglich halten und teure Befehle vermeiden. Früher waren vor allem Floating-Point Befehle teuer, die man oft durch die schnellere Integer-Arithmetik ersetzt hat, falls dies algorithmisch möglich war (diesen Rat findet man noch oft in der Literatur). Heute hat sich die Hardware so verbessert, dass im Allgemeinen nur noch die Floating-Point Division deutlich langsamer ist als die anderen Operatoren. Im obigen Beispiel der quadratischen Gleichung ist es daher sinnvoll, den Ausdruck &lt;br /&gt;
        p2 = -p / 2.0&lt;br /&gt;
:durch&lt;br /&gt;
        p2 = -0.5 * p&lt;br /&gt;
:zu ersetzen. Dadurch ersetzt man eine Division durch eine Multiplikation und spart außerdem das Negieren von &amp;lt;tt&amp;gt;p&amp;lt;/tt&amp;gt;, da der Compiler direkt mit &amp;lt;tt&amp;gt;-0.5&amp;lt;/tt&amp;gt; multipliziert.&lt;br /&gt;
;Ausnutzung der Prozessor-Pipeline: Moderne Prozessoren führen mehrere Befehle parallel aus. Dies ist möglich, weil jeder Befehl in mehrere Teilschritte zerlegt werden kann. Eine generische Unterteilung in vier Teilschritte ist z.B.:&lt;br /&gt;
:# Dekodieren des nächsten Befehls&lt;br /&gt;
:# Beschaffen der Daten, die der Befehl verwendet (aus Prozessorregistern, dem Cache, oder dem Hauptspeicher)&lt;br /&gt;
:# Ausführen des Befehls&lt;br /&gt;
:# Schreiben der Ergebnisse&lt;br /&gt;
:Man bezeichnet dies als die &amp;quot;[http://en.wikipedia.org/wiki/Instruction_pipeline instruction pipeline]&amp;quot; des Prozessors (heutige Prozessoren verwenden wesentlich feinere Unterteilungen). Prozessoren werden nun so gebaut, dass mehrere Befehle parallel, auf verschiedenen Ausführungsstufen ausgeführt werden. Wenn Befehl 1 also beim Schreiben der Ergebnisse angelangt ist, kann Befehl 2 die Hardware zum Ausführen des Befehls benutzen, während Befehl 3 seine Daten holt, und Befehl 4 soeben dekodiert wird. Unter bestimmten Bedingungen funktioniert diese Parallelverarbeitung jedoch nicht. Dies gibt Anlass zu Optimierungen:&lt;br /&gt;
:;Vermeiden unnötiger Typkonvertierungen: Der Prozessor verarbeitet Interger- und Floating-Point-Befehle in verschiedenen Pipelines, weil die Hardwareanforderungen sehr verschieden sind. Wird jetzt ein Ergebnis von Integer nach Floating-Point umgewandelt oder umgekehrt, muss die jeweils andere Pipeline warten, bis die erste Pipeline ihre Berechnung beendet. Es kann dann besser sein, Berechnungen in Floating-Point zu Ende zu führen, auch wenn sie semantisch eigentlich Integer-Berechnungen sind.&lt;br /&gt;
:;Reduzierung der Anzahl von Verzweigungen: Wenn der Code verzweigt (z.B. durch eine &amp;lt;tt&amp;gt;if&amp;lt;/tt&amp;gt;- oder  &amp;lt;tt&amp;gt;while&amp;lt;/tt&amp;gt;-Anweisung), ist nicht klar, welcher Befehl nach der Verzweigung ausgeführt werden soll, bevor Stufe 3 der Pipiline die Verzweigungsbedingung ausgewertet hat. Bis dahin wären die ersten beiden Stufen der Pipeline unbenutzt. Moderne Prozessoren benutzen zwar ausgefeilte Heuristiken, um das Ergebnis der Bedingung vorherzusagen, und führen den hoffentlich richtigen Zweig des Codes spekulativ aus, aber dies funktioniert nicht immer. Man sollte deshalb generell die Anzahl der Verzweigungen minimieren. Als Nebeneffekt führt dies meist auch zu besser lesbarem, verständlicherem Code. Im Matrixbeispiel kann man&lt;br /&gt;
       for j in range(N):&lt;br /&gt;
           jN = j*N&lt;br /&gt;
           for i in range(N):&lt;br /&gt;
               if i == j:&lt;br /&gt;
                    a[i + jN] = 1.0&lt;br /&gt;
               else:&lt;br /&gt;
                    a[i + jN] = 0.0&lt;br /&gt;
::durch&lt;br /&gt;
       for j in range(N):&lt;br /&gt;
           jN = j*N&lt;br /&gt;
           for i in range(N):&lt;br /&gt;
               a[i + jN] = 0.0&lt;br /&gt;
           a[j + jN] = 1.0&lt;br /&gt;
ersetzen. Die Diagonalelemente &amp;lt;tt&amp;gt;a[j + jN]&amp;lt;/tt&amp;gt; werden jetzt zwar zweimal initialisiert (in der Schleife auf Null, dann auf Eins), aber durch Elimination der &amp;lt;tt&amp;gt;if&amp;lt;/tt&amp;gt;-Abfrage wird dies wahrscheinlich mehr als ausgeglichen, zumal dadurch die innere Schleife wesentlich vereinfacht wurde.&lt;br /&gt;
;Ausnutzen des Prozessor-Cache: Zugriffe auf den Hauptspeicher sind sehr langsam. Deshalb werden stets ganze Speicherseiten auf einmal in den [http://en.wikipedia.org/wiki/Cache Cache] des Prozessors geladen. Wenn unmittelbar nacheinander benutzte Daten auch im Speicher nahe beieinander liegen (sogenannte &amp;quot;[http://en.wikipedia.org/wiki/Locality_of_reference locality of reference]&amp;quot;), ist die Wahrscheinlichkeit groß, dass die als nächstes benötigten Daten bereits im Cache sind und damit schnell gelesen werden können. Bei vielen Algorithmen kann man die Implementation so umordnen, dass die locality of reference verbessert wird, was zu einer drastischen Beschleunigung führt. Im Matrix-Beispiel ist z.B. die Reihenfolge der Schleifen wichtig. Für konstanten Index &amp;lt;tt&amp;gt;j&amp;lt;/tt&amp;gt; liegen die Indizes &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt; im Speicher hintereinander. Deshalb ist es günstig, in der inneren Schleife über &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt; zu iterieren:&lt;br /&gt;
       for j in range(N):&lt;br /&gt;
           jN = j*N&lt;br /&gt;
           for i in range(N):&lt;br /&gt;
               a[i + jN] = 0.0&lt;br /&gt;
           a[j + jN] = 1.0&lt;br /&gt;
:Die umgekehrte Reihenfolge der Schleifen ist hingegen ungünstig&lt;br /&gt;
       for i in range(N):&lt;br /&gt;
           for j in range(N):&lt;br /&gt;
               a[i + j*N] = 0.0&lt;br /&gt;
           a[i + i*N] = 1.0&lt;br /&gt;
:Jetzt werden in der inneren Schleife stets N Datenelemente übersprungen. Besonders bei großem N muss man daher häufig den Cache neu füllen, was bei der ersten Implementation nicht notwendig war.  (Ausserdem verliert man hier die Optimierung &amp;lt;tt&amp;gt;jN = j*N&amp;lt;/tt&amp;gt;, die jetzt nicht mehr möglich ist.)&lt;br /&gt;
&lt;br /&gt;
Als Faustregel kann man durch Optimierung eine Verdoppelung der Geschwindigkeit erreichen (in Ausnahmefällen auch mehr). Benötigt man stärkere Verbesserungen, muss man wohl oder übel einen besseren Algorithmus oder einen schnelleren Computer verwenden.&lt;br /&gt;
&lt;br /&gt;
== Algorithmen-Komplexität ==&lt;br /&gt;
&lt;br /&gt;
Komplexitätsbetrachtungen ermöglichen den Vergleich der prinzipiellen Eigenschaften von Algorithmen unabhängig von einer Implementation, Umgebung etc.&lt;br /&gt;
      &lt;br /&gt;
Eine einfache Möglichkeit ist das Zählen der Aufrufe einer Schlüsseloperation. Beispiel Sortieren:&lt;br /&gt;
* Anzahl der Vergleiche&lt;br /&gt;
* Anzahl der Vertauschungen&lt;br /&gt;
&lt;br /&gt;
=== Beispiel: Selection Sort ===&lt;br /&gt;
&lt;br /&gt;
  for i in range(len(a)-1):&lt;br /&gt;
    max = i&lt;br /&gt;
    for j in range(i+1, len(a)):&lt;br /&gt;
      if a[j] &amp;lt; a[max]:&lt;br /&gt;
        max = j&lt;br /&gt;
    a[max], a[i] = a[i], a[max]      # swap&lt;br /&gt;
&lt;br /&gt;
*Anzahl der Vergleiche: Ein Vergleich in jedem Durchlauf der inneren Schleife. Es ergibt sich folgende Komplexität:&lt;br /&gt;
*:Ingesamt &amp;lt;math&amp;gt;\sum_{i=0}^{N-2} \sum_{j=i+1}^{N-1}1 = \frac{N}{2} (N-1) \!&amp;lt;/math&amp;gt; Vergleiche.&lt;br /&gt;
&lt;br /&gt;
*Anzahl der Vertauschungen (swaps): Eine Vertauschung pro Durchlauf der äußeren Schleife:&lt;br /&gt;
*:Insgesamt &amp;lt;math&amp;gt;N-1 \!&amp;lt;/math&amp;gt; Vertauschungen&lt;br /&gt;
&lt;br /&gt;
Die Komplexität wird durch die Operationen bestimmt, die am häufigsten ausgeführt werden, hier also die Anzahl der Vergleiche. Die Anzahl der Vertauschungen ist hingegen kein geeignetes Kriterium für die Komplexität von selection sort, weil der Aufwand in der inneren Schleife ignoriert würde.&lt;br /&gt;
&lt;br /&gt;
=== Fallunterscheidung: Worst und Average Case ===&lt;br /&gt;
&lt;br /&gt;
Die Komplexität ist in der Regel eine Funktion der Eingabegröße (Anzahl der Eingabebits, Anzahl der Eingabeelemente). Sie kann aber auch von der Art der Daten abhängen, nicht nur von der Menge, z.B. vorsortierte Daten bei Quicksort. Um von der Art der Daten unabhängig zu werden, kann man zwei Fälle der Komplexität unterscheiden:&lt;br /&gt;
      &lt;br /&gt;
* Komplexität im ungünstigsten Fall &lt;br /&gt;
*: Der ungünstigste Fall ist die Eingabe gegebener Länge, für die der Algorithmus am langsamsten ist. Der Nachteil dieser Methode besteht darin, dass dieser ungünstige Fall in der Praxis vielleicht gar nicht oder nur selten vorkommt, so dass sich der Algorithmus in Wirklichkeit besser verhält als man nach dieser Analyse erwarten würde. Beim Quicksort-Algorithmus mit zufälliger Wahl des Pivot-Elements müsste z.B. stets das kleinste oder größte Element des aktuellen Intervalls als Pivot-Element gewählt werden, was äußerst unwahrscheinlich ist.&lt;br /&gt;
* Komplexität im durchschnittlichen/typischen Fall&lt;br /&gt;
*: Der typische Fall ist die mittlere Komplexität des Algorithmus über alle möglichen Eingaben. Dazu muss man die Wahrscheinlichkeit jeder möglichen Eingabe kennen, und berechnet dann die mittlere Laufzeit über dieser Wahrscheinlichkeitsverteilung. Leider ist die Wahrscheinlichkeit der Eingaben oft nicht bekannt, so dass man geeignete Annahmen treffen muss. Bei Sortieralgorithmen können z.B. alle möglichen Permutationen des Eingabearrays als gleich wahrscheinlich angenommen werden, und der typische Fall ist dann die mittlere Komplexität über alle diese Eingaben. Oft hat man jedoch in der Praxis andere Wahrscheinlichkeitsverteilungen, z.B. sind die Daten oft &amp;quot;fast sortiert&amp;quot; (nur wenige Elemente sind an der falschen Stelle). Dann verhält sich der Algorithmus ebenfalls anders als vorhergesagt.&lt;br /&gt;
&lt;br /&gt;
Wir beschränken uns in dieser Vorlesung auf die Komplexität im ungünstigseten Fall. '''Exakte''' Formeln für Komplexität sind aber auch dann schwer zu gewinnen, wie das folgende Beispiel zeigt:&lt;br /&gt;
&lt;br /&gt;
=== Beispiele aus den Übungen (Gemessene Laufzeiten für Mergesort/Selectionsort) ===&lt;br /&gt;
&lt;br /&gt;
* Mergesort: &amp;lt;math&amp;gt;\frac{0,977N\log N}{\log 2} + 0,267N-4.39 \!&amp;lt;/math&amp;gt;&lt;br /&gt;
*: andere Lösung: &amp;lt;math&amp;gt;1140 N\log(N) - 1819N + 6413 \!&amp;lt;/math&amp;gt;&lt;br /&gt;
* Selectionsort: &amp;lt;math&amp;gt;\frac{1}{2}N^2 - \frac{1}{2N} - 10^{-12} \!&amp;lt;/math&amp;gt;&lt;br /&gt;
*: andere Lösung: &amp;lt;math&amp;gt;1275N^2 - 116003^N + 11111144 \!&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Aus diesen Formeln wird nicht offensichtlich, welcher Algorithmus besser ist.&lt;br /&gt;
Näherung: Betrachte nur '''sehr große Eingaben''' (meist sind alle Algorithmen schnell genug für kleine Eingaben). Dieses Vorgehen wird als '''Asymptotische Komplexität''' bezeichnet (N gegen unendlich).&lt;br /&gt;
&lt;br /&gt;
=== Asymptotische Komplexität am Beispiel Polynom ===&lt;br /&gt;
&lt;br /&gt;
Polynom: &amp;lt;math&amp;gt;a\,x^2+b\,x+c=p\!&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;x \!&amp;lt;/math&amp;gt; sei die Eingabegröße, und wir betrachten die Entwicklung von &amp;lt;math&amp;gt;p \!&amp;lt;/math&amp;gt; in Abhängigkeit von &amp;lt;math&amp;gt;x \!&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;math&amp;gt;x=0 \!&amp;lt;/math&amp;gt;&lt;br /&gt;
*: &amp;lt;math&amp;gt;p=c \!&amp;lt;/math&amp;gt;&lt;br /&gt;
* &amp;lt;math&amp;gt;x=1 \!&amp;lt;/math&amp;gt;&lt;br /&gt;
*: &amp;lt;math&amp;gt;p=a+b+c \!&amp;lt;/math&amp;gt;&lt;br /&gt;
* &amp;lt;math&amp;gt;x=1000 \!&amp;lt;/math&amp;gt;&lt;br /&gt;
*: &amp;lt;math&amp;gt;p=1000000a+1000b+c \approx 1000000a\!&amp;lt;/math&amp;gt;&lt;br /&gt;
* &amp;lt;math&amp;gt;x \to \infty \!&amp;lt;/math&amp;gt;&lt;br /&gt;
*: &amp;lt;math&amp;gt;p \approx x^2a\!&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Für sehr große Eingaben verlieren also ''b'' und ''c'' immer mehr an Bedeutung, so dass am Ende nur noch ''a'' für die Komplexitätsbetrachtung wichtig ist.&lt;br /&gt;
&lt;br /&gt;
== Landau-Symbole ==&lt;br /&gt;
&lt;br /&gt;
Um die asymptotische Komplexität verschiedener Algorithmen miteinander vergleichen zu können, verwendet man die sogenannten [http://de.wikipedia.org/wiki/Landau-Symbole Landau-Symbole]. Das wichtigste Landau-Symbol ist &amp;lt;math&amp;gt;\mathcal{O}&amp;lt;/math&amp;gt;, mit dem man eine ''obere Schranke'' &amp;lt;math&amp;gt;f \in \mathcal{O}(g)&amp;lt;/math&amp;gt; für die Komplexität angeben kann. &lt;br /&gt;
&lt;br /&gt;
Schreibt man &amp;lt;math&amp;gt;f \in \Omega(g)&amp;lt;/math&amp;gt;, so stellt dies eine asymptotische ''untere Schranke'' für die Funktion f dar.&lt;br /&gt;
&lt;br /&gt;
Schließlich bedeutet &amp;lt;math&amp;gt;f \in \Theta(g)&amp;lt;/math&amp;gt;, dass die Funktion f genauso schnell wie die Funktion g wächst, das heißt man hat eine asymptotisch ''scharfe Schranke'' für f. Hierzu muss sowohl &amp;lt;math&amp;gt;f\in\mathcal{O}(g)&amp;lt;/math&amp;gt; als auch &amp;lt;math&amp;gt;f \in \Omega(g)&amp;lt;/math&amp;gt; erfüllt sein. &lt;br /&gt;
&lt;br /&gt;
Im nun folgenden soll auf die verschiedenen Landau-Symbole noch näher eingegeangen werden.&lt;br /&gt;
&lt;br /&gt;
===O-Notation===&lt;br /&gt;
&lt;br /&gt;
Intuitiv gilt: Für große N dominieren die am schnellsten wachsenden Terme einer Funktion. Die Notation &amp;lt;math&amp;gt;f \in \mathcal{O}(g)&amp;lt;/math&amp;gt; (sprich &amp;quot;f ist in O von g&amp;quot; oder &amp;quot;f ist von derselben Größenordnung wie g&amp;quot;) formalisiert eine solche Abschätzung der asymptotischen Komplexität der Funktion f von oben. &lt;br /&gt;
; Asymptotische Komplexität: Für zwei Funktionen f(x) und g(x) gilt&lt;br /&gt;
::&amp;lt;math&amp;gt;f(x) \in \mathcal{O}(g(x))&amp;lt;/math&amp;gt;&lt;br /&gt;
: genau dann wenn es eine Konstante &amp;lt;math&amp;gt;c&amp;gt;0&amp;lt;/math&amp;gt; und ein Argument &amp;lt;math&amp;gt;x_0&amp;lt;/math&amp;gt; gibt, so dass &lt;br /&gt;
::&amp;lt;math&amp;gt;\forall x \ge x_0:\quad f(x) \le c\,g(x)&amp;lt;/math&amp;gt;.&lt;br /&gt;
:Die Menge &amp;lt;math&amp;gt;\mathcal{O}(g(x))&amp;lt;/math&amp;gt; aller durch g(x) abschätzbaren Funktionen ist also formal definiert durch&lt;br /&gt;
::&amp;lt;math&amp;gt;\mathcal{O}(g(x)) = \{ f(x)\ |\ \exists c&amp;gt;0: \forall x \ge x_0: 0 \le f(x) \le c\,g(x)\}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Die Idee hinter dieser Definition ist, dass g(x) eine wesentlich einfachere Funktion ist als f(x), die sich aber nach geeigneter Skalierung (Multiplikation mit c) und für große Argumente x im wesentlichen genauso wie f(x) verhält. Man kann deshalb in der Algorithmenanalyse f(x) durch g(x) ersetzen. &amp;lt;math&amp;gt;f(x) \in \mathcal{O}(g(x))&amp;lt;/math&amp;gt; spielt für Funktionen eine ähnliche Rolle wie der Operator &amp;amp;le; für Zahlen: Falls a &amp;amp;le; b gilt, kann bei einer Abschätzung von oben ebenfalls a durch b ersetzt werden.&lt;br /&gt;
&lt;br /&gt;
==== Ein einfaches Beispiel ====&lt;br /&gt;
&lt;br /&gt;
[[Image:Sqsqrt.png]]&lt;br /&gt;
&lt;br /&gt;
Rot = &amp;lt;math&amp;gt;x^2 \!&amp;lt;/math&amp;gt;&lt;br /&gt;
Blau = &amp;lt;math&amp;gt;\sqrt{x} \!&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\sqrt{x} \in \mathcal{O}(x^2)\!&amp;lt;/math&amp;gt; weil &amp;lt;math&amp;gt;\sqrt{x} \le c\,x^2\!&amp;lt;/math&amp;gt; für alle &amp;lt;math&amp;gt;x \ge x_0 = 1 \!&amp;lt;/math&amp;gt; und &amp;lt;math&amp;gt;c = 1\!&amp;lt;/math&amp;gt;, oder auch für &amp;lt;math&amp;gt;x \ge x_0 = 4 \!&amp;lt;/math&amp;gt; und &amp;lt;math&amp;gt;c = 1/16&amp;lt;/math&amp;gt; (die Wahl von c und x&amp;lt;sub&amp;gt;0&amp;lt;/sub&amp;gt; in der Definition von O(.) ist beliebig, solange die Bedingungen erfüllt sind).&lt;br /&gt;
&lt;br /&gt;
==== Komplexität bei kleinen Eingaben ==== &lt;br /&gt;
&lt;br /&gt;
Algorithmus 1: &amp;lt;math&amp;gt;\mathcal{O}(N^2) \!&amp;lt;/math&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
Algorithmus 2: &amp;lt;math&amp;gt;\mathcal{O}(N\log{N}) \!&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Algorithmus 2 ist schneller (von geringerer Komplexität) für große Eingaben, aber bei kleinen Eingaben (insbesondere, wenn der Algorithmus in einer Schleife immer wieder mit kleinen Eingaben aufgerufen wird) könnte Algorithmus 1 schneller sein, falls der in der &amp;lt;math&amp;gt;\mathcal{O}&amp;lt;/math&amp;gt;-Notation verborgene konstante Faktor ''c'' bei Algorithmus 2 einen wesentlich größeren Wert hat als bei Algorithmus 1.&lt;br /&gt;
&lt;br /&gt;
==== Eigenschaften der O-Notation (Rechenregeln) ==== &lt;br /&gt;
&lt;br /&gt;
# Transitiv:&lt;br /&gt;
#: &amp;lt;math&amp;gt;f(x) \in \mathcal{O}(g(x)) \land g(x) \in \mathcal{O}(h(x)) \to f(x) \in \mathcal{O}(h(x)) \!&amp;lt;/math&amp;gt;           &lt;br /&gt;
# Additiv:&lt;br /&gt;
#: &amp;lt;math&amp;gt;f(x) \in \mathcal{O}(h(x)) \land g(x) \in \mathcal{O}(h(x)) \to f(x) + g(x) \in \mathcal{O}(h(x)) \!&amp;lt;/math&amp;gt;           &lt;br /&gt;
# Für Monome gilt:&lt;br /&gt;
#: &amp;lt;math&amp;gt;x^k \in \mathcal{O}(x^k)&amp;lt;/math&amp;gt; und&lt;br /&gt;
#: &amp;lt;math&amp;gt;x^k \in \mathcal{O}(x^{k+j}), \forall j \ge 0 \!&amp;lt;/math&amp;gt;&lt;br /&gt;
# Multiplikation mit einer Konstanten:&lt;br /&gt;
#: &amp;lt;math&amp;gt;f(x) \in \mathcal{O}(g(x)) \to c\,f(x) \in \mathcal{O}(g(x))\!&amp;lt;/math&amp;gt;&lt;br /&gt;
#: andere Schreibweise:&lt;br /&gt;
#: &amp;lt;math&amp;gt;f(x) = c\,g(x) \to f(x) \in \mathcal{O}(g(x))\!&amp;lt;/math&amp;gt;&lt;br /&gt;
# Folgerung aus 3. und 4. für Polynome: &lt;br /&gt;
#: &amp;lt;math&amp;gt;a_0+a_1\,x + ... + a_n\,x^n \in \mathcal{O}(x^n)\!&amp;lt;/math&amp;gt;&lt;br /&gt;
#: Beispiel: &amp;lt;math&amp;gt;a\,x^2+b\,x+c \in \mathcal{O}(x^2)\!&amp;lt;/math&amp;gt;&lt;br /&gt;
# Logarithmus:&lt;br /&gt;
#: &amp;lt;math&amp;gt;a, b &amp;gt; 1\!&amp;lt;/math&amp;gt;&lt;br /&gt;
#: &amp;lt;math&amp;gt;\log_{a}{x} \in \mathcal{O}(\log_{b}{x})\!&amp;lt;/math&amp;gt;&lt;br /&gt;
#: Die Basis des Logarithmus spielt also keine Rolle.&lt;br /&gt;
#: Beweis hierfür:&lt;br /&gt;
#:: &amp;lt;math&amp;gt;\log_{a}{x} = \frac{\log_{b}{x}}{\log_{b}{a}}\!&amp;lt;/math&amp;gt;&lt;br /&gt;
#:: Mit &amp;lt;math&amp;gt;c = 1 / \log_{b}{a}\,&amp;lt;/math&amp;gt; gilt: &amp;lt;math&amp;gt;\log_{a}{x} = c\,\log_{b}{x}\!&amp;lt;/math&amp;gt;.&lt;br /&gt;
#:: Wird hier die (zweite) Regel für Multiplikation mit einer Konstanten angewendet, fällt der konstante Faktor weg, also &amp;lt;math&amp;gt;\log_{a}{x} \in \mathcal{O}(\log_{b}{x})\!&amp;lt;/math&amp;gt;.&lt;br /&gt;
#: Insbesondere gilt auch &amp;lt;math&amp;gt;\log_{a}{x} \in \mathcal{O}(\log_{2}{x})\!&amp;lt;/math&amp;gt;, es kann also immer der 2er Logarithmus verwendet werden.&lt;br /&gt;
&lt;br /&gt;
==== O-Kalkül ==== &lt;br /&gt;
&lt;br /&gt;
Das O-Kalkül definiert wichtige Vereinfachungsregeln for Ausdrücke in O-Notation (Beweise: siehe Übungsaufgabe):&lt;br /&gt;
&lt;br /&gt;
# &amp;lt;math&amp;gt;f(x) \in \mathcal{O}(f(x))\!&amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt;\mathcal{O}(\mathcal{O}(f(x))) \in \mathcal{O}(f(x))\!&amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt;c\,\mathcal{O}(f(x)) \in \mathcal{O}(f(x))\,&amp;lt;/math&amp;gt; für jede Konstante ''c''&lt;br /&gt;
# &amp;lt;math&amp;gt;\mathcal{O}(f(x))+c \in \mathcal{O}(f(x))\,&amp;lt;/math&amp;gt; für jede Konstante ''c''&lt;br /&gt;
# Sequenzregel:&lt;br /&gt;
#: Wenn zwei nacheinander ausgeführte Programmteile die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(f(x))&amp;lt;/math&amp;gt; bzw. &amp;lt;math&amp;gt;\mathcal{O}(g(x))&amp;lt;/math&amp;gt; haben, gilt für beide gemeinsam:&lt;br /&gt;
#: &amp;lt;math&amp;gt;\mathcal{O}(f(x)) + \mathcal{O}(g(x)) \in \mathcal{O}(f(x))&amp;lt;/math&amp;gt; falls &amp;lt;math&amp;gt;g(x) &amp;lt; \mathcal{O}(f(x))&amp;lt;/math&amp;gt; bzw.&lt;br /&gt;
#: &amp;lt;math&amp;gt;\mathcal{O}(f(x)) + \mathcal{O}(g(x)) \in \mathcal{O}(g(x))\!&amp;lt;/math&amp;gt; falls &amp;lt;math&amp;gt;f(x) &amp;lt; \mathcal{O}(g(x))&amp;lt;/math&amp;gt;.&lt;br /&gt;
#: Informell schreibt man auch: &amp;lt;math&amp;gt;\mathcal{O}(f(x)) + \mathcal{O}(g(x)) \in \mathcal{O}(max(f(x), g(x)))\!&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Schachtelungsregel bzw. Aufrufregel:&lt;br /&gt;
#: Wenn in einer geschachtelten Schleife die äußere Schleife die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(f(x))&amp;lt;/math&amp;gt; hat, und die innere &amp;lt;math&amp;gt;\mathcal{O}(g(x))&amp;lt;/math&amp;gt;, gilt für beide gemeinsam:&lt;br /&gt;
#: &amp;lt;math&amp;gt;\mathcal{O}(f(x)) * \mathcal{O}(g(x)) \in \mathcal{O}(f(x) * g(x))\!&amp;lt;/math&amp;gt;.&lt;br /&gt;
#: Gleiches gilt wenn eine Funktion &amp;lt;math&amp;gt;\mathcal{O}(f(x))&amp;lt;/math&amp;gt;-mal aufgerufen wird, und die Komplexität der Funktion selbst &amp;lt;math&amp;gt;\mathcal{O}(g(x))&amp;lt;/math&amp;gt; ist.&lt;br /&gt;
&lt;br /&gt;
;Beispiel für 5.: Beide Schleifen haben die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt;. Dies gilt auch für ihre Hintereinanderausführung:&lt;br /&gt;
      for i in range(N):&lt;br /&gt;
          a[i] = i&lt;br /&gt;
      for i in range(N):&lt;br /&gt;
          print a[i]&lt;br /&gt;
;Beispiele für 6.: Beide Schleifen haben die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt;. Ihre Verschachtelung hat daher die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(N^2)&amp;lt;/math&amp;gt;. &lt;br /&gt;
      for i in range(N):&lt;br /&gt;
          for j in range(N):&lt;br /&gt;
              a[i*N + j] = i+j&lt;br /&gt;
: Dies gilt ebenso, wenn statt der inneren Schleife eine Funktion mit Komplexität &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; ausgeführt wird:&lt;br /&gt;
      for i in range(N):&lt;br /&gt;
          a[i] = foo(i, N)  # &amp;lt;math&amp;gt;\mathrm{foo}(i, N) \in \mathcal{O}(N)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== O-Kalkül auf das Beispiel des Selectionsort angewandt ==== &lt;br /&gt;
&lt;br /&gt;
Selectionsort: Wir hatten gezeigt dass &amp;lt;math&amp;gt;f(N) = \frac{N^2}{2} - \frac{N}{2}&amp;lt;/math&amp;gt;. Nach der Regel für Polynome vereinfacht sich dies zu &amp;lt;math&amp;gt;f(N) \in \mathcal{O}\left(\frac{N^2}{2}\right) = \mathcal{O}(N^2)\!&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Alternativ via Schachtelungsregel:&lt;br /&gt;
: Die äußere Schleife wird (''N''-1)-mal durchlaufen: &amp;lt;math&amp;gt;N-1 \in \mathcal{O}(N)&amp;lt;/math&amp;gt;&lt;br /&gt;
: Die innere Schleife wird (''N-i''-1)-mal durchlaufen. Das sind im Mittel ''N''/2 Durchläufe: &amp;lt;math&amp;gt;N/2 \in \mathcal{O}(N)&amp;lt;/math&amp;gt;&lt;br /&gt;
: Zusammen: &amp;lt;math&amp;gt;\mathcal{O}(N)*\mathcal{O}(N) \in \mathcal{O}(N^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nach beiden Vorgehensweisen kommen wir zur Schlussfolgerung, dass der Selectionsort die asymptotische Komplexität &amp;lt;math&amp;gt;\mathcal{O}(N^2)\!&amp;lt;/math&amp;gt; besitzt.&lt;br /&gt;
&lt;br /&gt;
==== Zusammenhang zwischen Komplexität und Laufzeit ==== &lt;br /&gt;
&lt;br /&gt;
Wenn eine Operation 1ms dauert, erreichen Algorithmen verschiedener Komplexität folgende Leistungen (wobei angenommen wird, dass der in der &amp;lt;math&amp;gt;\mathcal{O}&amp;lt;/math&amp;gt;-Notation verborgene konstante Faktor immer etwa gleich 1 ist):&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align:left&amp;quot; cellpadding=&amp;quot;7&amp;quot;&lt;br /&gt;
|+&lt;br /&gt;
|-&lt;br /&gt;
! Komplexität !! Operationen in 1s !! Operationen in 1min !! Operationen in 1h&lt;br /&gt;
|-&lt;br /&gt;
! &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt;&lt;br /&gt;
| 1000 || 60.000 || 3.600.000&lt;br /&gt;
|-&lt;br /&gt;
! &amp;lt;math&amp;gt;\mathcal{O}(N\log_2{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
| 140 || 4895 || 204094&lt;br /&gt;
|-&lt;br /&gt;
! &amp;lt;math&amp;gt;\mathcal{O}(N^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
| 32 || 245 || 1898&lt;br /&gt;
|-&lt;br /&gt;
! &amp;lt;math&amp;gt;\mathcal{O}(N^3)&amp;lt;/math&amp;gt;&lt;br /&gt;
| 10 || 39 || 153&lt;br /&gt;
|-&lt;br /&gt;
! &amp;lt;math&amp;gt;\mathcal{O}(2^N)&amp;lt;/math&amp;gt;&lt;br /&gt;
| 10 || 16 || 21&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==== Exponentielle Komplexität ==== &lt;br /&gt;
Der letzte Fall &amp;lt;math&amp;gt;\mathcal{O}(2^N)&amp;lt;/math&amp;gt; ist von exponentieller Komplexität. Das bedeutet, dass eine Verdopplung des Aufwands nur bewirkt, dass die maximale Problemgröße um eine Konstante wächst. Algorithmen mit exponentieller (oder noch höherer) Komplexität werden deshalb als '''ineffizient''' bezeichnet. Algorithmen mit höchstens polynomieller Komplexität gelten hingegen als effizient.&lt;br /&gt;
&lt;br /&gt;
In der Praxis sind allerdings auch polynomielle Algorithmen mit hohem Exponenten meist zu langsam. Als Faustregel kann man eine praktische Grenze von &amp;lt;math&amp;gt;\mathcal{O}(N^3)&amp;lt;/math&amp;gt; ansehen. Bei einer Komplexität von &amp;lt;math&amp;gt;\mathcal{O}(N^3)&amp;lt;/math&amp;gt; bewirkt ein verdoppelter Aufwand immer noch eine Steigerung der maximalen Problemgröße um den Faktor &amp;lt;math&amp;gt;\sqrt[3]{2}&amp;lt;/math&amp;gt; (also eine ''multiplikative'' Vergrößerung um ca. 25%, statt nur einer additiven Vergrößerung wie bei exponentieller Komplexität).&lt;br /&gt;
&lt;br /&gt;
===&amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;- Notation===&lt;br /&gt;
&lt;br /&gt;
Genauso wie &amp;lt;math&amp;gt;f \in \mathcal{O}(g)&amp;lt;/math&amp;gt; eine Art &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt;-Operator für Funktionen ist, definiert &amp;lt;math&amp;gt;f \in \Omega(g) &amp;lt;/math&amp;gt; eine Abschätzung von unten, analog zum &amp;lt;math&amp;gt;\ge&amp;lt;/math&amp;gt;-Operator für Zahlen. Formal kann man &amp;lt;math&amp;gt;f(N) \in \Omega(g(N)) &amp;lt;/math&amp;gt; genau dann schreiben, falls es eine Konstante &amp;lt;math&amp;gt; c &amp;gt; 0 &amp;lt;/math&amp;gt; gibt, so dass &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; f(N) \ge c \cdot g(N) &amp;lt;/math&amp;gt; für &amp;lt;math&amp;gt; N  \ge N_0 &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
gilt.&lt;br /&gt;
Man verwendet diese Notation also um abzuschätzen, wie groß der Aufwand (die Komplexität) für einen bestimmten Algorithmus ''mindestens'' ist und nicht ''höchstens'', was man mit der &amp;lt;math&amp;gt;\mathcal{O}&amp;lt;/math&amp;gt; - Notation ausdrücken würde.&lt;br /&gt;
&lt;br /&gt;
Ein praktisches Beispiel für eine Anwendung der &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;- Notation wäre die Fragestellung, ob es ''prinzipiell'' einen besseren Algorithmus für ein bestimmtes Problem gibt. Wie später im Abschnitt [[Suchen#Sortieren_als_Suchproblem|Sortieren als Suchproblem]] gezeigt wird, ist das Sortieren eines Arrays durch paarweise Vergleiche von Elementen immer mindestens von der Komplexität &amp;lt;math&amp;gt; \Omega(N\cdot \ln N) &amp;lt;/math&amp;gt;, was konkret bedeutet, dass kein Sortieralgorithmus, der nach diesem Prinzip arbeitet, jemals eine geringere Komplexität als beispielsweise Merge-Sort haben wird. Natürlich kann man den entsprechenden Sortieralgorithmus, also Merge-Sort zum Beispiel, unter Umständen noch optimieren, aber die Komplexität wird erhalten bleiben. Mit diesem Wissen kann man sich viel (vergebliche) Arbeit sparen.&lt;br /&gt;
&lt;br /&gt;
===&amp;lt;math&amp;gt;\Theta&amp;lt;/math&amp;gt;- Notation===&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;math&amp;gt;f(N) \in \Theta(g(N))&amp;lt;/math&amp;gt; ist eine scharfe Abschätzung der asymptotischen Komplexität einer Funktion f. &lt;br /&gt;
&lt;br /&gt;
Damit dies gilt, muss &amp;lt;math&amp;gt;f(N) \in \mathcal{O}(g(N))&amp;lt;/math&amp;gt; und ''gleichzeitig'' &amp;lt;math&amp;gt;f(N) \in \Omega(g(N))&amp;lt;/math&amp;gt; erfüllt sein.&lt;br /&gt;
&lt;br /&gt;
Dies ist natürlich auch die beste Abschätzung der asymptotischen Komplexität einer Funktion f. Formal bedeutet &amp;lt;math&amp;gt;f(N) \in \Theta(g(N))&amp;lt;/math&amp;gt; dass es zwei Konstanten &amp;lt;math&amp;gt; c_1 &amp;lt;/math&amp;gt; und &amp;lt;math&amp;gt; c_2 &amp;lt;/math&amp;gt;, beide größer als Null, gibt, so dass für alle &amp;lt;math&amp;gt; N \geq N_0 &amp;lt;/math&amp;gt; gilt: &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; c_1 \cdot g(N) \leq f(N) \leq c_2 \cdot g(N) &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
In der Praxis wird manchmal statt der &amp;lt;math&amp;gt;\Theta&amp;lt;/math&amp;gt;-Notation auch dann die &amp;lt;math&amp;gt;\mathcal{O}&amp;lt;/math&amp;gt;-Notation benutzt, wenn eine scharfe Schranke ausgedrückt werden soll. Dies ist zwar formal nicht korrekt, aber man kann die intendierte Bedeutung meist aus dem Kontext erschließen.&lt;br /&gt;
&lt;br /&gt;
== Komplexitätsvergleich zweier Algorithmen ==&lt;br /&gt;
&lt;br /&gt;
In diesem Abschnitt wollen wir der Frage nachgehen, wie ein formaler Beweis für die Behauptung &amp;lt;math&amp;gt; f(N) \in \mathcal{O}(g(N))&amp;lt;/math&amp;gt; geschehen kann. Hierbei werden zwei Beweismethoden vorgestellt werden, und zwar der '''Beweis über die Definition der Komplexität''' sowie der '''Beweis durch Dividieren'''.&lt;br /&gt;
&lt;br /&gt;
===Beweis über die Definition der asymptotischen Komplexität===&lt;br /&gt;
&lt;br /&gt;
Die Definition der asymptotischen Komplexität &amp;lt;math&amp;gt;f(N) \in \mathcal{O}(g(N))&amp;lt;/math&amp;gt; war: &lt;br /&gt;
&lt;br /&gt;
Es gibt eine Konstante &amp;lt;math&amp;gt; c &amp;gt; 0 &amp;lt;/math&amp;gt;, so dass &amp;lt;math&amp;gt; f(N) \le c \cdot g(N) &amp;lt;/math&amp;gt; für &amp;lt;math&amp;gt; N  \ge N_0 &amp;lt;/math&amp;gt; erfüllt ist. &lt;br /&gt;
&lt;br /&gt;
Um also die die asymptotische Komplexität &amp;lt;math&amp;gt;f(N) \in \mathcal{O}(g(N))&amp;lt;/math&amp;gt; zu beweisen, muss man die oben erwähnten Konstanten c und &amp;lt;math&amp;gt; N_0 &amp;lt;/math&amp;gt; finden, so dass &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; f(N) \leq c \cdot g(N) &amp;lt;/math&amp;gt; für alle &amp;lt;math&amp;gt; N \ge N_0 &amp;lt;/math&amp;gt; erfüllt ist. &lt;br /&gt;
&lt;br /&gt;
Dies geschieht zweckmäßigerweise mit dem Beweisprinzip der ''vollständigen Induktion''. Hierbei ist zu zeigen, dass&lt;br /&gt;
# &amp;lt;math&amp;gt; f(N_0) \leq g(N_0) &amp;lt;/math&amp;gt; für die eine zu bestimmende Konstante &amp;lt;math&amp;gt; N_0 &amp;lt;/math&amp;gt; gilt (''Induktionsanfang'') und &lt;br /&gt;
# falls &amp;lt;math&amp;gt; f(N) \leq g(N) &amp;lt;/math&amp;gt;, dann auch &amp;lt;math&amp;gt; f(N+1) \leq g(N+1) &amp;lt;/math&amp;gt; (''Induktionsschritt'') gilt.&lt;br /&gt;
&lt;br /&gt;
===Beweis durch Dividieren===&lt;br /&gt;
&lt;br /&gt;
Hierbei wählt man eine Konstante c und zeigt, dass &amp;lt;math&amp;gt; \lim_{N \rightarrow \infty} \frac{f(N)}{c \cdot g(N)} \leq 1 &amp;lt;/math&amp;gt; gilt (für die O-Notation, bei &amp;amp;Omega;-Notation gilt entsprechend &amp;lt;math&amp;gt;\geq 1 &amp;lt;/math&amp;gt;). Man kann dies auch als alternative Definition der Komplexität verwenden.&lt;br /&gt;
&lt;br /&gt;
Als Beispiel betrachten wir die beiden Funktionen &amp;lt;math&amp;gt; f(N) = N \,\lg N &amp;lt;/math&amp;gt; und &amp;lt;math&amp;gt; g(N) = N^2 &amp;lt;/math&amp;gt; und wollen zeigen, dass &amp;lt;math&amp;gt;f(N) \in \mathcal{O}(g(N))&amp;lt;/math&amp;gt; gilt. &lt;br /&gt;
&lt;br /&gt;
Als Konstante c wählen wir &amp;lt;math&amp;gt; c = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \lim_{N \rightarrow \infty} \frac{f(N)}{g(N)} = \lim_{N \rightarrow \infty} \frac{\lg N}{N} = \frac{\infty}{\infty} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Unbestimmte Ausdrücke der Form &lt;br /&gt;
&amp;lt;math&amp;gt; \lim_{x \rightarrow x_0} \frac{f(x)}{g(x)} &amp;lt;/math&amp;gt;,&lt;br /&gt;
in denen sowohl &amp;lt;math&amp;gt; f(x) &amp;lt;/math&amp;gt; als auch &amp;lt;math&amp;gt; g(x) &amp;lt;/math&amp;gt; mit &amp;lt;math&amp;gt; x \rightarrow x_0 &amp;lt;/math&amp;gt; gegen Null oder gegen Unendlich streben, kann man manchmal mit den Regeln von [http://de.wikipedia.org/wiki/L%27Hospital%27sche_Regel ''l'Hospital''] berechnen. Danach darf man die Funktionen f und g zur Berechnung des unbestimmten Ausdrucks durch ihre k-ten Ableitungen ersetzen:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \lim_{x \rightarrow x_0} \frac{f(x)}{g(x)} = \lim_{x \rightarrow x_0} \frac{f^{(k)}(x)}{g^{(k)}(x)} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In unserem Fall verwenden wir die erste Ableitung und erhalten:&lt;br /&gt;
&amp;lt;math&amp;gt; \lim_{N \rightarrow \infty} \frac{f'(x)}{g'(x)} = \lim_{N \rightarrow \infty} \frac{1/N}{1} \rightarrow 0 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Damit wurde &amp;lt;math&amp;gt;f(N) \in \mathcal{O}(g(N))&amp;lt;/math&amp;gt;, also &amp;lt;math&amp;gt;N \lg N \in \mathcal{O}(N^2)&amp;lt;/math&amp;gt; gezeigt.&lt;br /&gt;
&lt;br /&gt;
Man beachte hierbei, dass &amp;lt;math&amp;gt;N \lg N \in \mathcal{O}(N^2)&amp;lt;/math&amp;gt; keine enge Grenze für die Komplexität von &amp;lt;math&amp;gt;N \,\lg N&amp;lt;/math&amp;gt; darstellt, da der Grenzwert &amp;lt;math&amp;gt; \lim_{N \rightarrow \infty} \frac{f'(x)}{g'(x)}\, &amp;lt;/math&amp;gt; gegen 0 und nicht gegen eine von Null verschiedene Konstante strebt. In diesem Fall haben wir die Komplexität von &amp;lt;math&amp;gt;N \cdot \lg N &amp;lt;/math&amp;gt; also nur nach oben abschätzen können.&lt;br /&gt;
&lt;br /&gt;
===Beispiel für den Komplexitätsvergleich: Gleitender Mittelwert (Running Average)===&lt;br /&gt;
&lt;br /&gt;
Wir berechnen für ein gegebenes Array &amp;lt;tt&amp;gt;a&amp;lt;/tt&amp;gt; einen gleitenden Mittelwert über &amp;lt;tt&amp;gt;k&amp;lt;/tt&amp;gt; Elemente:&amp;lt;br/&amp;gt;&lt;br /&gt;
::&amp;lt;math&amp;gt;r_i = \frac{1}{k} \sum_{j=i-k+1}^i a_j&amp;lt;/math&amp;gt; &amp;lt;br/&amp;gt;&lt;br /&gt;
Das heisst, für jedes &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt; mitteln wir die letzten &amp;lt;tt&amp;gt;k&amp;lt;/tt&amp;gt; Elemente von &amp;lt;tt&amp;gt;a&amp;lt;/tt&amp;gt; und schreiben das Ergebnis in &amp;lt;tt&amp;gt;r[i]&amp;lt;/tt&amp;gt;. Diese Operation ist z.B. bei Börsenkursen wichtig: Neben dem aktuellen Kurs für jeden Tag wird dort meist auch der gleitende Mittelwert der letzten 30 Tage sowie der letzten 200 Tage angegeben. In diesen Mittelwerten erkennt man besser die langfristige Tendenz, weil die täglichen Schwankungen herausgemittelt werden. Wir nehmen außerdem an, dass&lt;br /&gt;
* Array-Zugriff hat eine Komplexit&amp;amp;auml;t von O(1)&lt;br /&gt;
* &amp;lt;math&amp;gt;k \ll N&amp;lt;/math&amp;gt;, d.h. &amp;lt;math&amp;gt;N-k\approx N&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Die beiden folgenden Algorithmen berechnen die Mittelwerte auf unterschiedliche Art. Der linke folgt der obigen Definition durch eine Summe, während der rechte inkrementell arbeitet: Man kann den Bereich der &amp;lt;tt&amp;gt;k&amp;lt;/tt&amp;gt; letzten Werte als Fenster betrachten, das über das Array &amp;lt;tt&amp;gt;a&amp;lt;/tt&amp;gt; geschoben wird. Schiebt man das Fenster ein Element weiter, fällt links ein Element heraus, und rechts kommt eins hinzu. Man muss also nicht jedes Mal die Summe neu berechnen, sondern kann den vorigen Wert aktualisieren. Wir werden sehen, dass dies Folgen für die Komplexität des Algorithmus hat.&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;0&amp;quot; cellpadding=&amp;quot;2&amp;quot; &lt;br /&gt;
|-&lt;br /&gt;
! Programmzeile&lt;br /&gt;
! Version 1: O(N * k)&lt;br /&gt;
! Komplexit&amp;amp;auml;t&lt;br /&gt;
! Version 2: O(N)&lt;br /&gt;
! Komplexit&amp;amp;auml;t&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
1.&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;tt&amp;gt;r = [0] * len(a)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(N)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;tt&amp;gt;r = [0] * len(a)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(N)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
2.&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;tt&amp;gt;if k &amp;gt; len(a):&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(1)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;tt&amp;gt;if k &amp;gt; len(a):&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(1)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
3.&lt;br /&gt;
|&lt;br /&gt;
:: &amp;lt;tt&amp;gt;raise RuntimeError (&amp;quot;k zu gro&amp;amp;szlig;&amp;quot;)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
|&lt;br /&gt;
:: &amp;lt;tt&amp;gt;raise RuntimeError (&amp;quot;k zu gro&amp;amp;szlig;&amp;quot;)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
4.&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;tt&amp;gt;for j in range(k-1, len(a)):&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;center&amp;gt;O(N-k+1) = '''O(N)'''&amp;lt;/center&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;tt&amp;gt;for i in range(k):&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(k)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
5.&lt;br /&gt;
|&lt;br /&gt;
:: &amp;lt;tt&amp;gt;for i in range(j-k+1, j+1):&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(k)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|&lt;br /&gt;
:: &amp;lt;tt&amp;gt;r[k-1] += a[i]&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(1)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
6.&lt;br /&gt;
|&lt;br /&gt;
:::: &amp;lt;tt&amp;gt;r[j] += a[i]&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(1)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;tt&amp;gt;for j in range(k, len(a)):&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;center&amp;gt;O(N-k+1) = '''O(N)'''&amp;lt;/center&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
7.&lt;br /&gt;
|&lt;br /&gt;
:: &amp;lt;tt&amp;gt;r[j] /= float(k)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(1)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|&lt;br /&gt;
:: &amp;lt;tt&amp;gt;r[j] = (a[j] - a[j-k] + r[j-1])&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(1)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
8.&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;tt&amp;gt;return r&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(1)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;tt&amp;gt;for j in range(len(a)):&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(N)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
9.&lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
|&lt;br /&gt;
:: &amp;lt;tt&amp;gt;r[j] /= float(k)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(1)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
10.&lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;tt&amp;gt;return r&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(1)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Wir zeigen unten dass Version 2 eine geringere Komplexit&amp;amp;auml;t besitzt, obwohl sie mehr Zeilen ben&amp;amp;ouml;tigt.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Wir haben in der Tabelle die Komplexität jeder Zeile für sich angegeben. Einfache Anweisungen (Berechnungen, Lese- und Schreibzugriffe auf das Array, Zuweiseungen) haben konstante Komplexität, die Komplexität des Schleifenkopfes allein (also der &amp;lt;tt&amp;gt;for&amp;lt;/tt&amp;gt;-Anweisung ohne den eingerückten Schleifenkörper) entspricht der Anzahl der Durchläufe. Wir müssen jetzt noch die Verschachtelung der Schleifen und die Nacheinanderausführung von Anweisungen berücksichtigen. &lt;br /&gt;
&lt;br /&gt;
====Berechnung der Komplexität von Version 1====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;small&amp;gt;(Wiederholung der Rechenregeln: siehe Abschnitt [[Effizienz#O-Notation|O-Notation]])&amp;lt;/small&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Wir betrachten zunächst die innere Schleife (Zeilen 5 und 6 von Version 1):&lt;br /&gt;
&lt;br /&gt;
Der Schleifenkopf (Zeile 5) hat die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(k)&amp;lt;/math&amp;gt;, weil die Schleife k-mal durchlaufen wird. Der Schleifenkörper (Zeile 6) hat die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;. Nach der Verschachtelungsregel müssen wir die beiden Komplexitäten multiplizieren, und es ergibt sich:&lt;br /&gt;
&lt;br /&gt;
::&amp;lt;math&amp;gt;\mathcal{O}(k)\cdot\mathcal{O}(1) = \mathcal{O}(k\cdot 1)=\mathcal{O}(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Wir betrachten nun die äußere Schleife. Der Schleifenkopf (Zeile 4) wird (N-k)-mal durchlaufen und hat somit eine Komplexität von &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt;. Der Schleifenkörper (Zeilen 5 bis 7) besteht aus der inneren Schleife (Zeilen 5 und 6) mit der gerade berechneten Komplexität &amp;lt;math&amp;gt;\mathcal{O}(k)&amp;lt;/math&amp;gt; sowie einer einfachen Anweisung (Zeile 7) mit Komplexität &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;. Nach der Sequenzregel wird die Komplexität des Schleifenkörpers durch Addition berechnet:&lt;br /&gt;
&lt;br /&gt;
::&amp;lt;math&amp;gt;\mathcal{O}(k)+\mathcal{O}(1) = \mathcal{O}(\max(k,1)) = \mathcal{O}(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Die Komplexität der gesamten äußeren Schleife erhalten wir nach der Verschachtelungsregel wieder durch multiplizieren:&lt;br /&gt;
&lt;br /&gt;
::&amp;lt;math&amp;gt;\mathcal{O}(N)\cdot\mathcal{O}(k) = \mathcal{O}(N\cdot k)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Die übrigen Schritte des Algorithmus werden einfach nacheinander ausgeführt, so dass sie ebenfalls nach der Sequenzregel behandelt werden. Wir erhalten&lt;br /&gt;
&lt;br /&gt;
::&amp;lt;math&amp;gt;\mathcal{O}(N)+\mathcal{O}(1)+\mathcal{O}(N\cdot k)+\mathcal{O}(1) = \mathcal{O}(\max(N,1,N\cdot k,1)) = \mathcal{O}(N\cdot k)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Der gesamte Algorithmus hat also die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(N\cdot k)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
====Berechnung der Komplexität von Version 2====&lt;br /&gt;
&lt;br /&gt;
Hier gibt es nur einfache Schleifen ohne Verschachtelung. Da der Schleifenkörper jeder Schleife nur einfache Anweisungen der Komplexität &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; enthält, ergibt sich die Komplexität der Schleifen nach der Verschachtelungsregel als&lt;br /&gt;
&lt;br /&gt;
::&amp;lt;math&amp;gt;\mathcal{O}(X)\cdot\mathcal{O}(1) = \mathcal{O}(X\cdot 1)=\mathcal{O}(X)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
wobei &amp;lt;math&amp;gt;\mathcal{O}(X)&amp;lt;/math&amp;gt; die Komplexität des jeweiligen Schleifenkopfes ist. Wir erhalten also für Zeilen 4 und 5: &amp;lt;math&amp;gt;\mathcal{O}(k)&amp;lt;/math&amp;gt;, Zeilen 6 und 7: &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt;, Zeilen 8 und 9: &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt;. Die Hintereinanderausführung wird nach der Sequenzregel behandelt:&lt;br /&gt;
&lt;br /&gt;
::&amp;lt;math&amp;gt;\mathcal{O}(N)+\mathcal{O}(1)+\mathcal{O}(k)+\mathcal{O}(N)+\mathcal{O}(N)+\mathcal{O}(1) = \mathcal{O}(\max(N,1,k,N,N,1)) = \mathcal{O}(N)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Dieser Algorithmus hat also nur die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
====Fazit====&lt;br /&gt;
&lt;br /&gt;
Obwohl Version 2 mehr Schritte benötigt hat sie eine geringere Komplexität, da die for-Schleifen nicht wie bei Version 1 verschachtelt/untergeordnet sind. Bei verschachtelten for-Schleifen muss die Multiplikationsregel angewendet werden &amp;amp;rarr; höhere Komplexität.&lt;br /&gt;
&lt;br /&gt;
Die gerade berechnete Komplexität gilt aber &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; unter der Annahme, dass Array-Zugriffe konstante Komplexität &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; besitzen. Wenn dies nicht der Fall ist, kann sich die Komplexität des Algorithmus drastisch verschlechtern.&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;0&amp;quot; cellpadding=&amp;quot;5&amp;quot; &lt;br /&gt;
|Allgemein gilt:&amp;lt;br/&amp;gt;&lt;br /&gt;
Algorithmen-Analysen beruhen auf der Annahme, dass Zugriffe auf die Daten optimal schnell sind, dass heißt, dass die für den jeweiligen Algorithmus am besten geeignete Datenstruktur verwendetet wird.&amp;lt;br /&amp;gt; &amp;amp;rarr; Ansonsten: Komplexitätsverschlechterung!&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Beispiel für eine Verschlechterung der Komplexität durch Verwendung einer nicht optimalen Datenstruktur====&lt;br /&gt;
&lt;br /&gt;
Wir verwende im Mittelwert-Algorithmus eine verkettete Liste anstelle des Eingabe-Arrays a&amp;lt;/u&amp;gt;. Wir benötigen dazu eine Funktion, die das j-te Element der Liste zurückgibt. Wie üblich ist die Liste mit Hilfe einer Knotenklasse implementiert:&lt;br /&gt;
      class Node:&lt;br /&gt;
          def __init__(self, data):&lt;br /&gt;
              self.data = data&lt;br /&gt;
              self.next = None&lt;br /&gt;
&lt;br /&gt;
Die Listenklasse selbst hat ein Feld &amp;lt;tt&amp;gt;head&amp;lt;/tt&amp;gt;, das eine Referenz auf den ersten Knoten speichert, und jeder Knoten speichert im Feld &amp;lt;tt&amp;gt;next&amp;lt;/tt&amp;gt; eine Referenz auf seinen Nachfolger. Um zum j-ten Element zu gelangen, muss man die Liste sequenziell durchlaufen&lt;br /&gt;
      def get_jth(list, j):&lt;br /&gt;
           r = list.head&lt;br /&gt;
           while j &amp;gt; 0:&lt;br /&gt;
               r = r.head&lt;br /&gt;
               j -= 1&lt;br /&gt;
           return r.data&lt;br /&gt;
Die Komplexität dieser Funktion ist offensichtlich &amp;lt;math&amp;gt;\mathcal{O}(j)&amp;lt;/math&amp;gt; (Komplexitätsberechnung wie oben). Wir setzen jetzt bei Version 1 des Mittelwert-Algorithmus diese Funktion in Zeile 6 anstelle des Indexzugriffs &amp;lt;tt&amp;gt;a[i]&amp;lt;/tt&amp;gt; ein (nur in dieser Zeile wird auf die Elemente des Arrays zugegriffen). Wir erhalten folgende Implementation (die Änderungen sind rot markiert):&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;0&amp;quot; cellpadding=&amp;quot;2&amp;quot; &lt;br /&gt;
|-&lt;br /&gt;
! Programmzeile&lt;br /&gt;
! Version 1 mit Liste: O(N * k)&lt;br /&gt;
! Komplexit&amp;amp;auml;t&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
1.&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;tt&amp;gt;r = [0] * len(a)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(N)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
2.&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;tt&amp;gt;if k &amp;gt; len(a):&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(1)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
3.&lt;br /&gt;
|&lt;br /&gt;
:: &amp;lt;tt&amp;gt;raise RuntimeError (&amp;quot;k zu gro&amp;amp;szlig;&amp;quot;)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
4.&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;tt&amp;gt;for j in range(k-1, len(a)):&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;center&amp;gt;O(N-k+1) = '''O(N)'''&amp;lt;/center&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
5.&lt;br /&gt;
|&lt;br /&gt;
:: &amp;lt;tt&amp;gt;for i in range(j-k+1, j+1):&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(k)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
6.&lt;br /&gt;
|&lt;br /&gt;
:::: &amp;lt;tt&amp;gt;r[j] += &amp;lt;font color=red&amp;gt;get_jth(a, i)&amp;lt;/font&amp;gt;&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;&amp;lt;font color=red&amp;gt;O(i)&amp;lt;/font&amp;gt;&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
7.&lt;br /&gt;
|&lt;br /&gt;
:: &amp;lt;tt&amp;gt;r[j] /= float(k)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(1)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
8.&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;tt&amp;gt;return r&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(1)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Der Aufruf der Funktion &amp;lt;tt&amp;gt;get_jth&amp;lt;/tt&amp;gt; ist jetzt gleichbedeutend mit einer dreifach verschachtelten Schleife (weil &amp;lt;tt&amp;gt;get_jth&amp;lt;/tt&amp;gt; ja eine zusatzliche Schleife enthält). Die Anzahl der Operationen in Zeile 4 bis 6 ist jetzt&lt;br /&gt;
&lt;br /&gt;
::&amp;lt;math&amp;gt;f(N,k)=\sum_{j=k-1}^{N-1}\,\sum_{i=j-k+1}^j\,\mathcal{O}(i)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
wobei das &amp;lt;math&amp;gt;\mathcal{O}(i)&amp;lt;/math&amp;gt; die neue Schleife durch Verwendung der Liste repräsentiert. Mit Mathematica-Hilfe [http://www.wolfram.com/] lässt sich diese Summe exakt ausrechnen&lt;br /&gt;
&lt;br /&gt;
::&amp;lt;math&amp;gt;f(N,k)=\frac{1}{2}(k N^2-k^2 N+k^2-k)\in \mathcal{O}(k N^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Die Komplexitätsberechnung erfolgte dabei nach der Regel für Polynome unter Beachtung von &amp;lt;math&amp;gt;k \ll N&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
====Fazit:====&lt;br /&gt;
&lt;br /&gt;
Die Komplexität von Version 1 mit einer verketteten Liste wäre O(N&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; * k)&lt;br /&gt;
'''&amp;amp;rarr; Die richtige Datenstruktur ist wichtig, da es sonst zu einer Komplexitätsverschlechterung kommen kann!'''&lt;br /&gt;
&lt;br /&gt;
Auf Version 2 unseres Running Average-Beispiels hätte eine verkettete Liste allerdings keine Auswirkungen, da die inkrementelle Berechnung der Summen in Zeile 7 weiterhin möglich ist (bei geschickter Implementation!) und somit Version 2 immer noch eine Komplexität von O(N) hätte.&lt;br /&gt;
&lt;br /&gt;
==Amortisierte Komplexität==&lt;br /&gt;
&lt;br /&gt;
Bis jetzt wurde die Komplexität nur im schlechtesten Fall (Worst Case) betrachtet. Bei einigen Algorithmen schwankt die Komplexität im schlechtesten Fall jedoch, wenn man die ungünstige Operation mehrmals hintereinander ausführt. Die amortisierte Komplexität beschäftigt sich mit der durchschnittlichen Komplexität über viele Aufrufe der ungünstigsten Operation.&lt;br /&gt;
&lt;br /&gt;
Zum weiter Lesen: [[http://de.wikipedia.org/wiki/Amortisierte_Laufzeitanalyse Wikipedia: Amortisierte Laufzeitanalyse]]&lt;br /&gt;
&lt;br /&gt;
===Beispiel: Inkrementieren von Binärzahlen===&lt;br /&gt;
&lt;br /&gt;
Frage: Angenommen, das Umdrehen eines Bits einer Binärzahl verursacht Kosten von 1 Einheit. Wir erzeugen die Folge der natürlichen Zahlen durch sukzessives Inkrementieren, von Null beginnend. Bei jeder Inkrementierung werden einige Bits verändert, aber diese Zahl (und damit die Kosten der Inkrementierungen) ''schwanken'' sehr stark. Wir fragen jetzt, was eine Inkrementierung im Durchschnitt kostet?&lt;br /&gt;
&lt;br /&gt;
Um diese Durchschnittskosten zu berechnen, bezahlen wir bei jeder Inkrementierung 2 Einheiten. Wenn davon nach Abzug der Kosten der jeweiligen Operation noch etwas übrig bleibt, wird der Rest dem  Guthaben zugeschrieben. Umgekehrt wird ein eventueller Fehlbetrag (wenn eine Inkrementierung mehr als 2 Bits umdreht) aus dem Guthaben gedeckt. Dadurch werden die ansonsten großen Schwankungen der Kosten ausgeglichen:&lt;br /&gt;
:: Kosten &amp;lt; Einzahlung &amp;amp;rarr; es wird gespart&lt;br /&gt;
:: Kosten = Einzahlung &amp;amp;rarr; Guthaben bleibt unverändert&lt;br /&gt;
:: Kosten &amp;gt; Einzahlung &amp;amp;rarr; Guthaben wird für die Kosten verbraucht&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;0&amp;quot; cellpadding=&amp;quot;5&amp;quot; &lt;br /&gt;
!Schritte&lt;br /&gt;
!Zahlen&lt;br /&gt;
!Kosten &amp;lt;br/&amp;gt;&lt;br /&gt;
(Anzahl der geänderten Bits)&lt;br /&gt;
! Einzahlung&lt;br /&gt;
!Guthaben =&amp;lt;br/&amp;gt;&lt;br /&gt;
altes Guthaben + Einzahlung - Kosten&lt;br /&gt;
|-&lt;br /&gt;
|1.&lt;br /&gt;
|0000&amp;lt;u&amp;gt;&amp;lt;span style=&amp;quot;color:#00BFFF;&amp;quot;&amp;gt;1&amp;lt;/span&amp;gt;&amp;lt;/u&amp;gt;&lt;br /&gt;
|1&lt;br /&gt;
|'''2'''&lt;br /&gt;
|'''1'''&lt;br /&gt;
|-&lt;br /&gt;
|2.&lt;br /&gt;
|000&amp;lt;u&amp;gt;&amp;lt;span style=&amp;quot;color:#00BFFF;&amp;quot;&amp;gt;1&amp;lt;/span&amp;gt;&amp;lt;/u&amp;gt;&amp;lt;u&amp;gt;&amp;lt;span style=&amp;quot;color:#00BFFF;&amp;quot;&amp;gt;0&amp;lt;/span&amp;gt;&amp;lt;/u&amp;gt;&lt;br /&gt;
|2&lt;br /&gt;
|'''2'''&lt;br /&gt;
|'''1'''&lt;br /&gt;
|-&lt;br /&gt;
|3.&lt;br /&gt;
|0001&amp;lt;u&amp;gt;&amp;lt;span style=&amp;quot;color:#00BFFF;&amp;quot;&amp;gt;1&amp;lt;/span&amp;gt;&amp;lt;/u&amp;gt;&lt;br /&gt;
|1&lt;br /&gt;
|'''2'''&lt;br /&gt;
|'''2'''&lt;br /&gt;
|-&lt;br /&gt;
|4.&lt;br /&gt;
|00&amp;lt;u&amp;gt;&amp;lt;span style=&amp;quot;color:#00BFFF;&amp;quot;&amp;gt;1&amp;lt;/span&amp;gt;&amp;lt;/u&amp;gt;&amp;lt;u&amp;gt;&amp;lt;span style=&amp;quot;color:#00BFFF;&amp;quot;&amp;gt;0&amp;lt;/span&amp;gt;&amp;lt;/u&amp;gt;&amp;lt;u&amp;gt;&amp;lt;span style=&amp;quot;color:#00BFFF;&amp;quot;&amp;gt;0&amp;lt;/span&amp;gt;&amp;lt;/u&amp;gt;&lt;br /&gt;
|3&lt;br /&gt;
|'''2'''&lt;br /&gt;
|'''1'''&lt;br /&gt;
|-&lt;br /&gt;
|5.&lt;br /&gt;
|0010&amp;lt;u&amp;gt;&amp;lt;span style=&amp;quot;color:#00BFFF;&amp;quot;&amp;gt;1&amp;lt;/span&amp;gt;&amp;lt;/u&amp;gt;&lt;br /&gt;
|1&lt;br /&gt;
|'''2'''&lt;br /&gt;
|'''2'''&lt;br /&gt;
|-&lt;br /&gt;
|6.&lt;br /&gt;
|001&amp;lt;u&amp;gt;&amp;lt;span style=&amp;quot;color:#00BFFF;&amp;quot;&amp;gt;10&amp;lt;/span&amp;gt;&amp;lt;/u&amp;gt;&lt;br /&gt;
|2&lt;br /&gt;
|'''2'''&lt;br /&gt;
|'''2'''&lt;br /&gt;
|-&lt;br /&gt;
|7.&lt;br /&gt;
|0011&amp;lt;u&amp;gt;&amp;lt;span style=&amp;quot;color:#00BFFF;&amp;quot;&amp;gt;1&amp;lt;/span&amp;gt;&amp;lt;/u&amp;gt;&lt;br /&gt;
|1&lt;br /&gt;
|'''2'''&lt;br /&gt;
|'''3'''&lt;br /&gt;
|-&lt;br /&gt;
|8.&lt;br /&gt;
|0&amp;lt;u&amp;gt;&amp;lt;span style=&amp;quot;color:#00BFFF;&amp;quot;&amp;gt;1000&amp;lt;/span&amp;gt;&amp;lt;/u&amp;gt;&lt;br /&gt;
|4&lt;br /&gt;
|'''2'''&lt;br /&gt;
|'''1'''&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Die Kosten ergeben sich aus der Anzahl der Ziffern die von 1 nach 0, bzw. von 0 nach 1 verändert werden&lt;br /&gt;
&lt;br /&gt;
&amp;lt;u&amp;gt;Rechnung:&amp;lt;/u&amp;gt;&lt;br /&gt;
&lt;br /&gt;
1. Schritt: Kosten: 1 &amp;lt; Einzahlung: 2&amp;lt;br /&amp;gt;&lt;br /&gt;
:: &amp;amp;rarr; es wird gespart&amp;lt;br /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. Schritt: Kosten: 2 = Einzahlung: 2&amp;lt;br /&amp;gt;&lt;br /&gt;
:: &amp;amp;rarr; es wird nicht gespart&amp;lt;br /&amp;gt;&lt;br /&gt;
:: &amp;amp;rarr; Guthaben bleibt so wie es ist &amp;lt;br /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. Schritt: Kosten: 1 &amp;lt; Einzahlung: 2&amp;lt;br /&amp;gt;&lt;br /&gt;
:: &amp;amp;rarr; es wird gespart&amp;lt;br /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
4. Schritt: Kosten: 3 &amp;gt; Einzahlung: 2&amp;lt;br /&amp;gt;&lt;br /&gt;
:: &amp;amp;rarr; es wird eine 1 vom Guthaben genommen um die Kosten zu zahlen&amp;lt;br /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
usw.&lt;br /&gt;
&lt;br /&gt;
Man erkennt, dass vor teuren Operation (Wechsel von 3 auf 4 bzw. von 7 auf 8) genügend Guthaben angespart wurde, um die Kosten zu decken. Das Guthaben geht bei diesen Operationen immer wieder auf 1 zurück, aber es wird nie vollständig verbraucht (Dies kann natürlich auch mathematisch exakt bewiesen werden, wie wir es unten am Beispiel des dynamische Arrays zeigen). Wir schließen daraus, dass die durchschnittlichen oder '''amortisierten Kosten''' einer Inkrementierungsoperation gleich 2 sind.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Zum weiter Lesen: [[http://de.wikipedia.org/wiki/Account-Methode Wikipedia Account-Methode]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
====Fazit====&lt;br /&gt;
Die amortisierte Komplexität beschäftigt sich mit dem Durchschnitt aller Operation im ungünstigsten Fall. Operationen mit hohen Kosten, die aber nur selten ausgeführt werden, fallen bei der amortisierten Komplexität nicht so ins Gewicht. Bei Algorithmen, die gelegentlich eine &amp;quot;teure&amp;quot; Operation benutzen, ansonsten jedoch &amp;quot;billigen&amp;quot; Operationen aufrufen, kann die amortisierte Komplexität niedriger sein als die Komplexität im schlechtesten (Einzel-)Fall.&lt;br /&gt;
&lt;br /&gt;
In unserem Beispiel fallen die teuren Einzelschritte (z.B. 4. und 8. Schritt) bei den amortisierten Kosten nicht so ins Gewicht, da wir die Kosten aus unserem Guthaben mitbezahlen können. Das Guthaben ist immer groß genug, weil jeder zweite Aufruf eine billige Operation ist, die nur ein Bit umdreht und somit das Ansparen ermöglichen. Diese Betrachtung zeigt, dass die amortisierte (d.h. durchschnittliche) Komplexität des Algoithmus niedriger (nämlich konstant) ist als die Komplexität im schlechtesten Fall.&lt;br /&gt;
&lt;br /&gt;
===statisches Array===&lt;br /&gt;
&lt;br /&gt;
Ein statisches Array hat eine feste Größe N und das Erweitern des Arrays um ein neues Element besitzt eine Komplexität von O(N), &lt;br /&gt;
denn es muss ein neues Array mit der Größe N+1 erzeugt werden.&amp;lt;br /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;u&amp;gt;Anhängen eines weiteren Elements an ein statisches Array:&amp;lt;/u&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;0&amp;quot; cellpadding=&amp;quot;5&amp;quot; align=&amp;quot;right&amp;quot;&lt;br /&gt;
!Schritte&lt;br /&gt;
|'''Array'''&lt;br /&gt;
&amp;lt;small&amp;gt;(wie es aussehen könnte)&amp;lt;/small&amp;gt;&lt;br /&gt;
!Komplexität&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;center&amp;gt;altes Array&amp;lt;/center&amp;gt;&lt;br /&gt;
|&amp;lt;center&amp;gt;[0,1,2,3]&amp;lt;/center&amp;gt;&lt;br /&gt;
|&amp;lt;center&amp;gt;-&amp;lt;/center&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|1. Array N+1&lt;br /&gt;
|&amp;lt;center&amp;gt;[None,None,None,None,None]&amp;lt;/center&amp;gt;&lt;br /&gt;
|&amp;lt;center&amp;gt;O(N+1) = '''O(N)'''&amp;lt;/center&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|2. Kopieren&lt;br /&gt;
|&amp;lt;center&amp;gt;[0,1,2,3,None]&amp;lt;/center&amp;gt;&lt;br /&gt;
|&amp;lt;center&amp;gt;'''O(N)'''&amp;lt;/center&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|3. append von &amp;quot;x&amp;quot;&lt;br /&gt;
|&amp;lt;center&amp;gt;[0,1,2,3,'x']&amp;lt;/center&amp;gt;&lt;br /&gt;
|&amp;lt;center&amp;gt;'''O(1)'''&amp;lt;/center&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
altesArray = [0,1,2,3]&amp;lt;br /&amp;gt;&lt;br /&gt;
altesArray.append('x')&lt;br /&gt;
&lt;br /&gt;
1. Es wird ein neues Array der Größe N+1 erzeugt&amp;lt;br /&amp;gt;&lt;br /&gt;
2. Die Daten aus dem alten Array werden in das neue Array mit der Länge N+1 kopiert&amp;lt;small&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
(Die Operation besitzt nur eine Komplexität von O(N), wenn  das Kopieren eines Elements eine Komplexität von O(1) besitzt)&amp;lt;/small&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
3. 'x' wird an die letzte Stelle des neuen Arrays geschrieben&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;u&amp;gt;Additionsregel:&amp;lt;/u&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
O(N) + O(N) + O(1) &amp;amp;isin; O(N), falls O(1) &amp;amp;isin; O(N) [O(max(O(N),O(1))] &amp;lt;small&amp;gt;(Bedingung: N &amp;gt; 1)&amp;lt;/small&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===dynamisches Array===&lt;br /&gt;
&lt;br /&gt;
Beim dynamischen Array werden mehr Speicherelemente reserviert als zur Zeit benötigt. Wir unterscheiden deshalb &lt;br /&gt;
&lt;br /&gt;
::&amp;lt;tt&amp;gt;capacity&amp;lt;/tt&amp;gt; = Anzahl der möglichen Elemente, die in das Array passen&amp;lt;br /&amp;gt;&lt;br /&gt;
::&amp;lt;tt&amp;gt;size&amp;lt;/tt&amp;gt; = Anzahl der Elemente, die im Array zur Zeit gespeichert sind&amp;lt;br /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Die Daten selbst werden in einem statischen Array gespeichert:&lt;br /&gt;
::&amp;lt;tt&amp;gt;data&amp;lt;/tt&amp;gt; = statisches Array der Größe &amp;quot;capacity&amp;quot;&amp;lt;br /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ist dieses Array voll, wird ein neues Datenarray der doppelten Größe allokiert, und die Daten werden vom alten ins neue Array kopiert. Die Vorgehensweise &lt;br /&gt;
beim Zufügen eines neuen Elements im Fall (size == capacity) ist also&lt;br /&gt;
* capacity wird verdoppelt&amp;lt;br /&amp;gt;&lt;br /&gt;
: neue capacity = 2 * alte capacity&lt;br /&gt;
: (allgemein genügt es auch, wenn capacity wird um einen bestimmten Prozentsatz vergrößert wird, &lt;br /&gt;
:: neue capacity = alte capacity * c (mit c &amp;gt; 1, z.B. c = 1.2)&lt;br /&gt;
* ein neues statisches Array der Größe 'neue capacity' wird erzeugt&lt;br /&gt;
* das alte Array wird ins neue kopiert und danach freigegeben&lt;br /&gt;
* das anzufügende Element wird ins neue Array eingefügt&lt;br /&gt;
&lt;br /&gt;
'''Folge:''' Die Kosten für das Vergrößern der Kapazität werden amortisiert über viele Einfügungen, die kein Vergrößern erfordern. Die Operation &amp;lt;tt&amp;gt;append&amp;lt;/tt&amp;gt; besitzt amortisierte Komplexität  O(1), wie wir jetzt zeigen:&lt;br /&gt;
&lt;br /&gt;
===Analyse des dynamischen Arrays===&lt;br /&gt;
&lt;br /&gt;
Durchschnitt der Gesamtkosten für N-maliges append = &amp;lt;math&amp;gt;\frac{1}{N} \sum_{i = 1}^N Kosten(i)&amp;lt;/math&amp;gt;. Zur Analyse der amortisierten Komplexität wird ein Potential&amp;lt;br/&amp;gt;&lt;br /&gt;
::&amp;amp;Phi;&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; = 2*size&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; - capacity&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
eingeführt, wobei das Array nach dem i-ten Einfüge-Schritt die Größe size&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; und die Kapizität capacity&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; hat. Wir nehmen vereinfachend an, dass es keine Löschoperationen gibt. Dann gilt nach dem i-ten Schritt jeweils&lt;br /&gt;
::&amp;amp;Phi;&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; = 2*i - capacity&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;u&amp;gt;Fall 1: Array ist nicht voll&amp;lt;/u&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
Es wird kein Umkopieren benötigt, da das Array noch nicht voll ist&amp;lt;br/&amp;gt; &lt;br /&gt;
&amp;amp;rarr; size&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt; &amp;lt; capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt;&amp;lt;br/&amp;gt; &lt;br /&gt;
&amp;amp;rarr; capacity&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; == capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Kosten: 1 (für Einfügen des neuen Elements)&amp;lt;br /&amp;gt;&lt;br /&gt;
Potenzial vor append: &amp;amp;Phi;&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt; = 2(i - 1) - capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
Potenzial nach append: &amp;amp;Phi;&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; = 2i - capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
amortisierte Kosten = Kosten&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; + &amp;amp;Phi;&amp;lt;sub&amp;gt;(i)&amp;lt;/sub&amp;gt; - &amp;amp;Phi;&amp;lt;sub&amp;gt;(i-1)&amp;lt;/sub&amp;gt;&lt;br /&gt;
:::::                 = 1            + (2i - capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt;)        - [2(i - 1) - capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt;]&lt;br /&gt;
:::::                 = 1            + 2i - capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt;         - 2i + 2 + capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt;&lt;br /&gt;
:::::                 = 1            + &amp;lt;del&amp;gt;2i&amp;lt;/del&amp;gt; - &amp;lt;del&amp;gt;capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt;&amp;lt;/del&amp;gt; - &amp;lt;del&amp;gt;2i&amp;lt;/del&amp;gt; + 2 + &amp;lt;del&amp;gt;capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt;&amp;lt;/del&amp;gt;&lt;br /&gt;
:::::                 = 1 + 2&lt;br /&gt;
:::::                 = 3 = O(1) &amp;amp;rarr; konstant&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;u&amp;gt;Fall 2: Array ist voll&amp;lt;/u&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
Vor dem i-ten append muss umkopiert werden&amp;lt;br/&amp;gt; &lt;br /&gt;
&amp;amp;rarr; size&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt; == capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt; == i-1&amp;lt;br/&amp;gt; &lt;br /&gt;
&amp;amp;rarr; Allokieren eines neuen statischen Arrays mit verdoppelter Kapazität notwendig, also capacity&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; == 2*capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Kosten: (i-1) + 1 (für Umkopieren und Einfügen des neuen Elements)&amp;lt;br /&amp;gt;&lt;br /&gt;
Potenzial vor append =  &amp;amp;Phi;&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt; = 2(i - 1) - capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
Potenzial nach append =  &amp;amp;Phi;&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; =  2i - 2 capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
amortisierte Kosten = Kosten&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; + &amp;amp;Phi;&amp;lt;sub&amp;gt;(i)&amp;lt;/sub&amp;gt; - &amp;amp;Phi;&amp;lt;sub&amp;gt;(i-1)&amp;lt;/sub&amp;gt;&lt;br /&gt;
:::::               = ((i - 1) + 1) + 2i - 2 capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt; - [2(i-1) - capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt;]&lt;br /&gt;
:::::               = i + &amp;lt;del&amp;gt;2i&amp;lt;/del&amp;gt; - 2 capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt; - &amp;lt;del&amp;gt;2i&amp;lt;/del&amp;gt; + 2 - capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt;&lt;br /&gt;
:::::               = i + 2 - capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt;&lt;br /&gt;
:::::               = i + 2 - (i - 1) &amp;lt;small&amp;gt;(da capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt; = i-1)&amp;lt;/small&amp;gt;&lt;br /&gt;
:::::               = 3 = O(1) &amp;amp;rarr; konstant            &lt;br /&gt;
&lt;br /&gt;
'''Damit wurde bewiesen, dass die Operation &amp;lt;tt&amp;gt;append&amp;lt;/tt&amp;gt; beim dynamischen Array eine amortisierte Komplexität von 3 Einheiten hat, also &amp;lt;tt&amp;gt;append&amp;lt;/tt&amp;gt; &amp;amp;isin; O(1)'''. Diese Operation kann deshalb gefahrlos in der inneren Schleife eines Algorithmus benutzt werden.&lt;br /&gt;
&lt;br /&gt;
==== Beispiel für 9 Einfügeoperationen ====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;0&amp;quot; cellpadding=&amp;quot;5&amp;quot;&lt;br /&gt;
!Array&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;small&amp;gt;(wie es aussehen könnte)&amp;lt;/small&amp;gt;&lt;br /&gt;
!size&lt;br /&gt;
!capacity&lt;br /&gt;
!Kosten für append&amp;lt;br /&amp;gt;(einschließlich Umkopieren)&lt;br /&gt;
!Summe Kosten&lt;br /&gt;
!Durchschnittskosten&lt;br /&gt;
!&amp;amp;Phi;&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; = 2 * size - capacity&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;small&amp;gt;(i = size)&amp;lt;/small&amp;gt;&lt;br /&gt;
!Potenzialdifferenz&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;amp;Delta; &amp;amp;Phi;&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; = &amp;amp;Phi;&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; - &amp;amp;Phi;&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt;&lt;br /&gt;
!amortisierte Kosten&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
= Kosten&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; + &amp;amp;Delta; &amp;amp;Phi;&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;center&amp;gt;[None]&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;0&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;1&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;-&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;-&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;-&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;-1&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;-&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;-&amp;lt;/center&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;center&amp;gt;[a]&amp;lt;/center&amp;gt;&amp;lt;center&amp;gt;&amp;lt;span style=&amp;quot;color:#00BFFF;&amp;quot;&amp;gt;Array ist voll!&amp;lt;/span&amp;gt;&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;1&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;1&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;1&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;1&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;1&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;1&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;2&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;3&amp;lt;/center&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;center&amp;gt;[a,b]&amp;lt;/center&amp;gt;&amp;lt;center&amp;gt;&amp;lt;span style=&amp;quot;color:#00BFFF;&amp;quot;&amp;gt;Array ist voll!&amp;lt;/span&amp;gt;&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;2&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;2&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;1 + 1&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;3&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;3/2&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;2&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;1&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;3&amp;lt;/center&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;center&amp;gt;[a,b,c,None]&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;3&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;4&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;2 + 1&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;6&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;6/3&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;2&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;0&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;3&amp;lt;/center&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;center&amp;gt;[a,b,c,d]&amp;lt;/center&amp;gt;&amp;lt;center&amp;gt;&amp;lt;span style=&amp;quot;color:#00BFFF;&amp;quot;&amp;gt;Array ist voll!&amp;lt;/span&amp;gt;&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;4&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;4&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;1&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;7&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;7/4&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;4&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;2&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;3&amp;lt;/center&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;center&amp;gt;[a,b,c,d,e,None,None,None]&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;5&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;8&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;4 + 1&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;12&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;12/5&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;2&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;-2&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;3&amp;lt;/center&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;center&amp;gt;[a,b,c,d,e,f,None,None]&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;6&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;8&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;1&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;13&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;13/6&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;4&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;2&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;3&amp;lt;/center&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;center&amp;gt;[a,b,c,d,e,f,g,None]&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;7&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;8&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;1&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;14&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;14/7&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;6&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;2&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;3&amp;lt;/center&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;center&amp;gt;[a,b,c,d,e,f,g,h]&amp;lt;/center&amp;gt;&amp;lt;center&amp;gt;&amp;lt;span style=&amp;quot;color:#00BFFF;&amp;quot;&amp;gt;Array ist voll!&amp;lt;/span&amp;gt;&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;8&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;8&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;1&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;15&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;15/8&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;8&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;2&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;3&amp;lt;/center&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;center&amp;gt;[a,b,c,d,e,f,g,h,j,None,None,None,&amp;lt;br /&amp;gt;&lt;br /&gt;
None,None,None,None]&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;9&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;16&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;8 + 1&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;24&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;24/9&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;2&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;-6&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;3&amp;lt;/center&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Die durchschnittlichen Kosten betragen stets etwa 2 Einheiten, schwanken allerdings so, dass nicht unmittelbar ersichtlich ist, ob dies für sämtliche Einfügeoperationen gilt. Die amortisierte Komplexität, die mit Hilfe des Potentials berechnet wird, ist hingegen konstant 3, wie auch im obigen Beweis für alle Einfügeoperationen allgemein gezeigt wurde.&lt;br /&gt;
&lt;br /&gt;
[[Suchen|Nächstes Thema]]&lt;/div&gt;</summary>
		<author><name>Jschleic</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Korrektheit&amp;diff=4727</id>
		<title>Korrektheit</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Korrektheit&amp;diff=4727"/>
				<updated>2010-08-10T12:12:42Z</updated>
		
		<summary type="html">&lt;p&gt;Jschleic: /* Prinzipien für die Generierung von Testdaten */ typo&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Man unterscheidet zwischen Prüfung der Korrektheit (Verifikation) und Prüfung der Spezifikation (Validierung). Ein Algorithmus heißt korrekt, wenn er sich gemäß seiner Spezifikation verhält, auch wenn seine Spezifikation nicht immer die gewünschten Ergebnisse liefert. Die Spezifikation beschreibt die Vorbedingungen (was vor der Anwendung des Algorithmus gilt, so dass der Algorithmus überhaupt angewendet werden darf) und die Nachbedingungen (was nach der Anwendung des Algorithmus gilt, welchen Zustand des Systems der Algorithmus also erzeugt). Hier geht es ausschliesslich um die Prüfung der Korrektheit eines Algorithmus, also darum, ob die spezifizierten Nachbedingungen wirklich gelten.&lt;br /&gt;
 &lt;br /&gt;
Nebenbemerkungen&lt;br /&gt;
# es gibt Algorithmen, die ''nie'' mit einer 100-prozentigen Wahrscheinlichkeit richtige Ergebnisse liefern können (z.B. [http://en.wikipedia.org/wiki/Primality_test#Probabilistic_tests nichtdeterministische Primzahltests]). &lt;br /&gt;
# '''Korrektheit''' wird in Algorithmenbüchern meist nur im Zusammenhang mit konkreten Algorithmen behandelt, aber nicht als übergreifendes Problem. Dies erscheint der Bedeutung von Korrektheit nicht angemessen.&lt;br /&gt;
&lt;br /&gt;
Will man die Korrektheit eines Algorithmus/Programms feststellen, hat man 3 Vorgehensweisen zur Verfügung: Prüfung der syntaktischen Korrektheit, formaler Korrektheitsbeweis und Softwaretest.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Syntaktische Korrektheit ==&lt;br /&gt;
&lt;br /&gt;
Die syntaktische Korrektheit behandeln wir hier nur kurz und der Vollständigkeit halber. Sie wird in den Veranstaltungen zur theoretischen Informatik (Grammatiken) und zum Compilerbau ausführlich behandelt.&lt;br /&gt;
&lt;br /&gt;
=== Syntaktische Prüfung ===&lt;br /&gt;
Es wird eine Grammatik definiert, deren Regeln die Implementation des Algorithmus befolgen muss. Für ein Programm heißt das beispielsweise, dass die Syntax der Programmiersprache eingehalten werden muss.&lt;br /&gt;
&lt;br /&gt;
Vorteile des Verfahrens: die Richtigkeit der Syntax lässt sich leicht vom Compiler/Interpreter überprüfen (mehr dazu in der Theoretischen Informatik und Compilerbau). Somit ist es die einfachste Möglichkeit, viele inkorrekte Programme schnell zu erkennen und zurückzuweisen.&lt;br /&gt;
  &amp;gt;&amp;gt;&amp;gt; if a==0&lt;br /&gt;
    File &amp;quot;&amp;lt;stdin&amp;gt;&amp;quot;, line 1&lt;br /&gt;
      if a==0&lt;br /&gt;
            ^&lt;br /&gt;
  SyntaxError: invalid syntax&lt;br /&gt;
&lt;br /&gt;
=== Typprüfung ===&lt;br /&gt;
Ein Typ definiert Gruppierung der Daten und die Operationen, die für diese Datengruppierung erlaubt sind(konkreter Typ) bzw. die Bedeutung der Daten und die erlaubten Operationen (abstrakter Datentyp, vgl. Dreieck aus der [[Einführung#Definition von Datenstrukturen|ersten Vorlesung]]). Typen sind Zusicherungen an den Algorithmus und den Compiler/Interpreter, dass Daten und deren Operationen bestimmte semantische Bedingungen einhalten. Wenn man innerhalb des Algorithmus mit Typen arbeitet, darf man von der semantischen Korrektheit der erlaubten Operationen ausgehen. Umgekehrt können Operationen, die zu Typkonflikten führen würden, leicht als inkorrekt zurückgeweisen werden.&lt;br /&gt;
&lt;br /&gt;
Vorteile des Verfahrens: Typprüfung ist teuerer als syntaktische Prüfung, aber billiger als andere Prüfungen der Korrektheit (mehr dazu im Kapitel [[Generizität]]).&lt;br /&gt;
  &amp;gt;&amp;gt;&amp;gt; a=3&lt;br /&gt;
  &amp;gt;&amp;gt;&amp;gt; b=None&lt;br /&gt;
  &amp;gt;&amp;gt;&amp;gt; a+b&lt;br /&gt;
  Traceback (most recent call last):&lt;br /&gt;
    File &amp;quot;&amp;lt;stdin&amp;gt;&amp;quot;, line 1, in &amp;lt;module&amp;gt;&lt;br /&gt;
  TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'&lt;br /&gt;
&lt;br /&gt;
In python ist (ebenso wie in vielen anderen Programmiersprachen) explizite Typprüfung möglich:&lt;br /&gt;
  &amp;gt;&amp;gt;&amp;gt; import types&lt;br /&gt;
  &amp;gt;&amp;gt;&amp;gt; a=3&lt;br /&gt;
  &amp;gt;&amp;gt;&amp;gt; b=None&lt;br /&gt;
  &amp;gt;&amp;gt;&amp;gt; if isinstance(b, types.IntType): # prüft, ob b ein Integer ist&lt;br /&gt;
  ...     print a+b&lt;br /&gt;
  ... else:&lt;br /&gt;
  ...     raise TypeError, &amp;quot;b ist kein Integer&amp;quot; # falls b kein Integer ist, wird ein TypeError ausgelöst&lt;br /&gt;
  ... &lt;br /&gt;
 &lt;br /&gt;
  Traceback (most recent call last):&lt;br /&gt;
    File &amp;quot;&amp;lt;stdin&amp;gt;&amp;quot;, line 4, in &amp;lt;module&amp;gt;&lt;br /&gt;
  TypeError: b ist kein Integer&lt;br /&gt;
&lt;br /&gt;
== Formaler Korrektheitsbeweis ==&lt;br /&gt;
=== (Halb-)Automatisches Beweisen ===&lt;br /&gt;
Man versucht, die Hypothese H: ''Algorithmus ist korrekt'' entweder mathematisch zu beweisen oder zu widerlegen. Dieses Beweisverfahren heißt dann halbautomatisch, wenn der Mensch in den Entscheidungsprozess miteinbezogen wird.&lt;br /&gt;
&lt;br /&gt;
Um den Beweis durchführen zu können, ist folgendes nötig:&lt;br /&gt;
;eine [http://en.wikipedia.org/wiki/Formal_specification formale Spezifikation] des Algorithmus: eine formale Spezifikation wird in einer [http://en.wikipedia.org/wiki/Specification_language Spezifikationssprache] geschrieben (z.B. [http://en.wikipedia.org/wiki/Z_notation Z]). Sie ist &lt;br /&gt;
:* deklarativ (d.h. beschreibt, was das Programm tun soll, ist selbst aber nicht ausführbar)&lt;br /&gt;
:* formal präzise (kann nur auf eine einzige Weise interpretiert werden)&lt;br /&gt;
:* hierarchisch aufgebaut (eine Spezifikation für einen komplizierten Algorithmus greift auf Spezifikationen für einfache Bestandteile dieses Algorithmus zurück)&lt;br /&gt;
:* so einfach, dass ihre Korrektheit für einen Menschen mit entsprechender Erfahrung unmittelbar einsichtig ist (denn eine Spezifikation kann nicht formal bewiesen werden - dafür wäre eine weitere Spezifikation nötig, die auch bewiesen werden müsste usw.)&lt;br /&gt;
;ein axiomatisiertes Programmiermodell: zum Beispiel&lt;br /&gt;
:* eine axiomatisierbare Programmiersprache, wie z.B. WHILE-Programm (s. [[Einführung#Zur Frage der elementaren Schritte|erste Vorlesung]]), Pascal (siehe dazu Hoare's [http://delivery.acm.org/10.1145/70000/63445/cb-p153-hoare.pdf?key1=63445&amp;amp;key2=5041959021&amp;amp;coll=ACM&amp;amp;dl=ACM&amp;amp;CFID=15151515&amp;amp;CFTOKEN=6184618 grundlegenden Artikel]) und rein funktionale Programmiersprachen&lt;br /&gt;
:* ein axiomatisierbares Subset einer Programmiersprache (die meisten Programmiersprachen sind zu komplex, um als Ganzes axiomatisierbar zu sein)&lt;br /&gt;
:* endliche Automaten&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Der Korrektheitsbeweis kann beispielsweise mit dem Hoare-Kalkül (Hoare-Logik) durchgeführt werden (Hoare erfand u.a. den Quicksort-Algorithmus). Diese Methode wurde in &lt;br /&gt;
:  C.A.R. Hoare: ''&amp;quot;An Axiomatic Basis for Computer Programming&amp;quot;'', Communications of the ACM, 1969 [http://www.cs.ucsb.edu/~kemm/courses/cs266/hoare69.pdf] &lt;br /&gt;
erstmalig beschrieben. Im folgenden wird das Verfahren an einem Beispiel erläutert.&lt;br /&gt;
&lt;br /&gt;
==== Beispiel-Algorithmus ====&lt;br /&gt;
Zuerst brauchen wir einen Algorithmus, den wir auf Korrektheit prüfen wollen. Wir nehmen als Beispiel die Division x/y durch sukzessives Subtrahieren.&lt;br /&gt;
&lt;br /&gt;
 Vorbedingungen:&lt;br /&gt;
    int x,y&lt;br /&gt;
   0 &amp;lt; y &amp;lt;= x&lt;br /&gt;
 Gesucht:&lt;br /&gt;
    Quotient q, Rest r&lt;br /&gt;
 Algorithmus:&lt;br /&gt;
    r = x&lt;br /&gt;
    q = 0&lt;br /&gt;
    while y &amp;lt;= r:&lt;br /&gt;
        r = r - y&lt;br /&gt;
        q = q + 1&lt;br /&gt;
 Nachbedingungen:&lt;br /&gt;
    x == r + y*q and r &amp;lt; y&lt;br /&gt;
&lt;br /&gt;
==== Aufbau der Hoare-Logik ====&lt;br /&gt;
&lt;br /&gt;
Grundlegende syntaktische Struktur:&lt;br /&gt;
: p {Q} r&lt;br /&gt;
mit '''p''':Vorbedingung, '''Q''': Operation, '''r''': Nachbedingung.&lt;br /&gt;
Es bedeutet also schlicht: wenn man im Zustand '''p''' ist und eine Operation '''Q''' ausführt, kommt man in den Zustand '''r'''. Hat eine Operation keine Vorbedingung, schreibt man &lt;br /&gt;
: true {Q} r&lt;br /&gt;
&lt;br /&gt;
Die Hoare-Logik besteht aus 5 Axiomen:&lt;br /&gt;
;D0 - Axiom der Zuweisung: (Rule of Assignment)&lt;br /&gt;
:: R[t] {x=t} R[x]&lt;br /&gt;
  &lt;br /&gt;
: '''Beispiel:''' t==5 {x=t} x==5&lt;br /&gt;
&lt;br /&gt;
:Vorbedingung und Nachbedingung sind gleich, mit Ausnahme der Variablen x und t, die in der Zuweisung verknüpft werden: Man erhält die Vorbedingung, wenn man in der Nachbedingung alle Vorkommen von x (bzw. allgemein: alle Vorkommen der linken Variable der Zuweisung) durch t (bzw. allgemein: durch die rechte Variable der Zuweisung) ersetzt.&lt;br /&gt;
&lt;br /&gt;
;D1 - Konsequenzregeln: (Rules of Consequence, besteht aus zwei Axiomen)&lt;br /&gt;
:'''D1(a):''' wenn gilt&lt;br /&gt;
:: P {Q} R und R &amp;amp;rArr; S&lt;br /&gt;
:dann gilt auch&lt;br /&gt;
:: P {Q} S&lt;br /&gt;
:'''D1(b):''' wenn gilt &lt;br /&gt;
:: P {Q} R und S &amp;amp;rArr; P&lt;br /&gt;
:dann gilt auch&lt;br /&gt;
:: S {Q} R&lt;br /&gt;
:'''Beispiel:''' Für jede ganze Zahl gilt (x&amp;gt;5) &amp;amp;rArr; (x&amp;gt;0). Gilt außerdem (x&amp;gt;5) dann gilt erst recht (x&amp;gt;0).&lt;br /&gt;
&lt;br /&gt;
;D2 - Sequenzregel: (Rule of Composition)&lt;br /&gt;
:wenn gilt&lt;br /&gt;
:: P {Q&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;} R&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; und R&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; {Q&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;} R &lt;br /&gt;
:dann gilt auch&lt;br /&gt;
:: P {Q&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;, Q&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;} R&lt;br /&gt;
:Das heißt: wenn man P hat und Q&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; darauf anwendet, kommt man zu R&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;. Wenn man R&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; hat und Q&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; darauf anwendet, kommt man zu R. Deshalb kann man das so verkürzen: wenn man P hat und nacheinander Q&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; und Q&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; darauf anwendet, kommt man zu R.&lt;br /&gt;
&lt;br /&gt;
;D3 - Iterationsregel: (Rule of Iteration)&lt;br /&gt;
:wenn gilt&lt;br /&gt;
:: (P &amp;amp;and; B) {S} P&lt;br /&gt;
:dann gilt auch&lt;br /&gt;
:: P { while B do S } (&amp;amp;not;B &amp;amp;and; P)&lt;br /&gt;
:P wird dabei als '''Schleifeninvariante''' bezeichnet, weil es sowohl in der Vor- als auch in der Nachbedingung gilt. B ist die '''Schleifenbedingung''' - solange B erfüllt ist, wird die Schleife weiter ausgeführt.&lt;br /&gt;
&lt;br /&gt;
Da wir in dem Divisions-Algorithmus mit dem Typ '''int''' arbeiten, brauchen wir außerdem die für diesen Typ erlaubten Operationen, also die Axiome der ganzen Zahlen.&lt;br /&gt;
: '''A1:''' Kommutativität  x+y=y+x, x*y=y*x&lt;br /&gt;
: '''A2:''' Assoziativität  (x+y)+z=x+(y+z), (x*y)*z=x*(y*z)&lt;br /&gt;
: '''A3:''' Distributivität  x*(y+z)=x*y+x*z&lt;br /&gt;
: '''A4:''' Subtraktion (Inverses Element)  y&amp;amp;le;x &amp;amp;rArr; (x-y)+y=x&lt;br /&gt;
: '''A5:''' Neutrale Elemente  x+0=x, x*0=0, x*1=x&lt;br /&gt;
&lt;br /&gt;
==== Beweisen des Algorithmus ====&lt;br /&gt;
Vorbedingung: 0 &amp;lt; y,x&lt;br /&gt;
&lt;br /&gt;
Schleifeninvariante P (gleichzeitig Nachbedingung): x == y*q + r&lt;br /&gt;
  (1)  true &amp;amp;rArr; x==x+y*0                                          y*0==0 und x==x+0 folgen aus A5&lt;br /&gt;
  (2)  x==x+y*0              {r=x}                  x==r+y*0     D0: ersetze x durch r&lt;br /&gt;
  (3)  x==r+y*0              {q=0}                  x==r+y*q     D0: ersetze 0 durch q&lt;br /&gt;
  (4)  true                  {r=x}                  x==r+y*0     D1(b): kombiniere (1) und (2)&lt;br /&gt;
  (5)  true                  {r=x, q=0}             x==r+y*q     D2: kombiniere (4) und (3)&lt;br /&gt;
  (6)  x==r+y*q &amp;amp;and; y=r &amp;amp;rArr; x==(r-y)+y*(1+q)                       folgt aus A1...A5&lt;br /&gt;
  (7)  x==(r-y)+y*(1+q)      {r=r-y}                x==r+y*(1+q) D0: ersetze (r-y) durch r&lt;br /&gt;
  (8)  x==r+y*(1+q)          {q=q+1}                x==r+y*q     D0: ersetze (q+1) durch q&lt;br /&gt;
  (9)  x==(r-y)+y*(1+q)      {r=r-y, q=q+1}         x==r+y*q     D2: kombiniere (7) und (8)&lt;br /&gt;
  (10) x==r+y*q &amp;amp;and; y&amp;amp;le;r        {r=r-y, q=q+1}         x==r+y*q     D1(b): kombiniere (6) und (9)&lt;br /&gt;
  (11) x==r+y*q    {while y&amp;amp;le;r do (r=r-y, q=q+1)} x==r+y*q &amp;amp;and; &amp;amp;not;(y&amp;amp;le;r) D3: transformiere (10)&lt;br /&gt;
  (12) true        {r=x, q=0, &lt;br /&gt;
                    while y&amp;amp;le;r do (r=r-y, q=q+1)} x==r+y*q &amp;amp;and; &amp;amp;not;(y&amp;amp;le;r) D2: kombiniere (5) und (11)&lt;br /&gt;
&lt;br /&gt;
Im obigen Beweis ergibt sich sogar ''true'' als Vorbedingung (i.e. es gibt keine Vorbedingung). Dies liegt daran, dass Hoare in seinem Artikel durchweg von nicht-negativen Zahlen ausgeht. Diese Annahme wird beim Beweis von Zeile (6) benutzt.&lt;br /&gt;
&lt;br /&gt;
In der Praxis führt man solche Beweise natürlich nicht von Hand, sondern benutzt geeignete Programme, sogenannte [http://en.wikipedia.org/wiki/Automated_theorem_proving automatische Beweiser], die man allerding oft interaktiv steuern muss, weil der Beweis ohne diese Hilfe zu lange dauern würde.&lt;br /&gt;
&lt;br /&gt;
=== (Halb-)Automatisches Verfeinern ===&lt;br /&gt;
Dieses Verfahren ist beliebter, als das (halb-)automatische Beweisen. Die formale Spezifikation wird nach bestimmten, semantik-erhaltenden Transformationsregeln in ein ausführbares Programm umgewandelt. Mehr dazu z.B. in der [http://en.wikipedia.org/wiki/Program_refinement Wikipedia (Program refinement)]. Der Vorteil dieser Methode besteht darin, dass man die Transformationsregeln so definieren kann, dass nur das axiomatisierte Subset der Zielsprache benutzt wird. Dadurch wird der Korrektheitsbeweis stark vereinfacht.&lt;br /&gt;
&lt;br /&gt;
==Software-Tests==&lt;br /&gt;
&lt;br /&gt;
Dijkstra [http://de.wikipedia.org/wiki/Edsger_Wybe_Dijkstra] ließ einmal den Satz verlauten: &amp;quot;Tests können nie die Abwesenheit von Fehlern beweisen [Anwesenheit schon]&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Nach solch einer Aussage stellt sich die Frage, ob es sich überhaupt lohnt, mit dem Testverfahren die Korrektheit eines Algorithmus zu zeigen. Es erscheint einem doch plausibler sich auf die &amp;quot;formalen Methoden&amp;quot; zu berufen, mit dem Wissen, dass diese uns tatsächlich einen Beweis liefern können, ob nun H oder nicht H gilt. Zudem kommt noch erschwerend hinzu, dass es bei Tests bisher keine Theorie gibt, die sicherstellt, dass das Testprogramm einen vorhandenen Fehler zumindest mit hoher Wahrscheinlichkeit findet.&lt;br /&gt;
&lt;br /&gt;
Ein [http://de.wikipedia.org/wiki/Softwaretest Software-Test] versucht, ein Gegenbeispiel zur Hypothese H &amp;quot;der Algorithmus ist korrekt&amp;quot; zu finden. Dabei gibt es 4 Möglichkeiten:&lt;br /&gt;
 &lt;br /&gt;
   Algorithmus	   Testantwort	&lt;br /&gt;
      +	               +	        Algorithmus ist richtig, kein Gegenbeispiel gefunden&lt;br /&gt;
      -	               -	        Alg. ist falsch, und der Test erkennt den Fehler&lt;br /&gt;
      +	               -	        Bug im Test (Gegenbeispiel, obwohl Alg. richtig ist)&lt;br /&gt;
      -	               +	        Test hat versagt, da er den Fehler im Alg. nicht erkannt hat&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Wenn ein Gegenbeispiel zu H gefunden wird, kann man den Algorithmus (oder den Test) debuggen. Wird hingegen keines gefunden, nimmt man an, dass der Algorithmus korrekt ist. Man sieht, dass diese Annahme im Fall 4 nicht stimmt. Da Softwaretests jedoch in der Praxis sehr erfolgreich verwendet werden, ist dieser Fall offenbar nicht so häufig, dass man das Testen als Methode generell ablehnen müßte.&lt;br /&gt;
&lt;br /&gt;
=== Beispiel für das Testen: Freivalds Algorithmus ===&lt;br /&gt;
&lt;br /&gt;
Wir wollen die Wahrscheinlichkeit, dass ein Test einen vorhandenen Fehler übersieht, am Beispiel des [http://en.wikipedia.org/wiki/Freivald's_algorithm Algorithmus von Freivald] studieren. Es handelt sich dabei um einen randomisierten Algorithmus zum Testen der Matrixmultiplikation (siehe J. Hromkovič: ''&amp;quot;Randomisierte Algorithmen&amp;quot;'', Teubner 2004). Ziel dieses Algorithmuses ist es, die Hypothese H: &amp;quot;C ist das Produkt der Matrizen A und B&amp;quot; durch ein Gegenbeispiel zu widerlegen, wobei der Test einen anderen Algorithmus verwendet, um Vergleichsdaten zu gewinnen.&lt;br /&gt;
&lt;br /&gt;
  gegeben:&lt;br /&gt;
       Matrizen A, B, C  der Größe NxN &lt;br /&gt;
       Testhypothese H:  &amp;lt;tt&amp;gt;A*B == C&amp;lt;/tt&amp;gt;  Matrixmultiplikation (d.h. C wurde vorher durch C = mmul(A, B) berechnet, &lt;br /&gt;
                                            wobei mmul() der zu testende Multiplikationsalgorithmus ist).&lt;br /&gt;
 &lt;br /&gt;
  (1) Initialisierung      &lt;br /&gt;
       wähle Zufallsvektor der Länge N aus Nullen und Einsen: &amp;lt;math&amp;gt;\alpha \in \{0, 1\}^N &amp;lt;/math&amp;gt;  &lt;br /&gt;
  (2) Matrix-Vektor-Multiplikation (keine Matrix-Matrix-Multiplikation, denn die soll ja gerade verifiziert werden)&lt;br /&gt;
 &lt;br /&gt;
       &amp;lt;math&amp;gt;\left.\begin{array}{l}&lt;br /&gt;
                \beta = B*\alpha \\&lt;br /&gt;
                \gamma=A*\beta&lt;br /&gt;
                \end{array}\right\}A*(B*\alpha) == (A*B)*\alpha&lt;br /&gt;
       &amp;lt;/math&amp;gt; &lt;br /&gt;
 &lt;br /&gt;
       &amp;lt;math&amp;gt;\delta=C*\alpha&amp;lt;/math&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
  (3) Test der Korrektheit: falls &amp;lt;tt&amp;gt;A*B == C&amp;lt;/tt&amp;gt;, liefert der folgende Test stets &amp;lt;tt&amp;gt;true&amp;lt;/tt&amp;gt;:&lt;br /&gt;
 &lt;br /&gt;
       return   γ==δ&lt;br /&gt;
&lt;br /&gt;
Wir analysieren nun, mit welcher Wahrscheinlichkeit der Algorithmus den Fehler findet, wenn es denn einen gibt, d.h.&lt;br /&gt;
   &lt;br /&gt;
*Wahrscheinlichkeit '''p''', dass Freivalds Algorithmus den Fehler findet&amp;lt;br/&amp;gt;&lt;br /&gt;
oder&amp;lt;br/&amp;gt;&lt;br /&gt;
*Wahrscheinlichkeit '''q = 1 - p''', dass Freivalds Algorithmus den Fehler '''nicht''' findet.&lt;br /&gt;
&lt;br /&gt;
Wir schätzen diese Wahrscheinlichkeit ab für den einfachen Fall N=2. Wir definieren:&lt;br /&gt;
    &lt;br /&gt;
   &amp;lt;math&amp;gt;C=&lt;br /&gt;
  \begin{pmatrix} &lt;br /&gt;
    c_{11} &amp;amp; c_{12}  \\ &lt;br /&gt;
    c_{21} &amp;amp; c_{22}  &lt;br /&gt;
  \end{pmatrix},\qquad&lt;br /&gt;
\alpha=\begin{pmatrix}&lt;br /&gt;
    \alpha_1 \\&lt;br /&gt;
    \alpha_2 &lt;br /&gt;
     \end{pmatrix},\qquad&lt;br /&gt;
 \delta=\begin{pmatrix}&lt;br /&gt;
    \delta_1 \\&lt;br /&gt;
    \delta_2&lt;br /&gt;
 \end{pmatrix}&lt;br /&gt;
  = \begin{pmatrix}&lt;br /&gt;
    c_{11}\alpha_1 + c_{12}\alpha_2 \\&lt;br /&gt;
    c_{21}\alpha_1 + c_{22}\alpha_2&lt;br /&gt;
   \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Fallunterscheidung:'''&lt;br /&gt;
      &lt;br /&gt;
'''Fall 1:'''  C enthält genau 1 Fehler, z.B. &amp;lt;math&amp;gt;c_{11}&amp;lt;/math&amp;gt; hat falschen Wert&lt;br /&gt;
&lt;br /&gt;
:Der Fehler wird gefunden, wenn &amp;lt;math&amp;gt;\delta_1 \ne \gamma_1 \Leftrightarrow\alpha_1\ne 0&amp;lt;/math&amp;gt;. Da &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt; eine Zufallszahl aus &amp;lt;math&amp;gt;\{0,1\}&amp;lt;/math&amp;gt; ist, folgt daraus, dass '''p''' = '''q''' = &amp;lt;math&amp;gt;\frac{1}{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
       &lt;br /&gt;
'''Fall 2:'''  C enthält 2 Fehler&lt;br /&gt;
:(a)   in verschiedenen Zeilen und Spalten, z.B. &amp;lt;math&amp;gt;c_{11}&amp;lt;/math&amp;gt; und &amp;lt;math&amp;gt;c_{22}&amp;lt;/math&amp;gt;. Es gilt: Der Fehler in &amp;lt;math&amp;gt;c_{11}&amp;lt;/math&amp;gt; wird gefunden, wenn &amp;lt;math&amp;gt;\delta_1 \ne \gamma_1 \Leftrightarrow \alpha_1\ne 0&amp;lt;/math&amp;gt;. Unabhängig davon wird der Fehler in &amp;lt;math&amp;gt;c_{22}&amp;lt;/math&amp;gt; gefunden, wenn &amp;lt;math&amp;gt;\delta_2 \ne \gamma_2 \Leftrightarrow \alpha_2\ne 0&amp;lt;/math&amp;gt;. Da &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt; und &amp;lt;math&amp;gt;\alpha_2&amp;lt;/math&amp;gt; statistisch unabhängig sind, ist die Wahrscheinlichkeit für jedes dieser Ereignisse &amp;lt;math&amp;gt;q_1&amp;lt;/math&amp;gt; bzw. &amp;lt;math&amp;gt;q_2&amp;lt;/math&amp;gt; jeweils &amp;lt;math&amp;gt;\frac{1}{2}&amp;lt;/math&amp;gt;, und die Gesamtwahrscheinlichkeit '''q''', dass ''keiner'' der beiden Fehler gefunden wird, ist deren Produkt: '''q''' = &amp;lt;math&amp;gt;q_1*q_2 = \frac{1}{2}* \frac{1}{2} = \frac{1}{4}&amp;lt;/math&amp;gt;.        &lt;br /&gt;
&lt;br /&gt;
:(b) in verschiedenen Zeilen, gleichen Spalten, z.B. &amp;lt;math&amp;gt;c_{11}&amp;lt;/math&amp;gt; und &amp;lt;math&amp;gt;c_{21}&amp;lt;/math&amp;gt;. Es gilt: Der Fehler in &amp;lt;math&amp;gt;c_{11}&amp;lt;/math&amp;gt; wird gefunden, wenn &amp;lt;math&amp;gt;\delta_1 \ne \gamma_1 \Leftrightarrow \alpha_1\ne 0&amp;lt;/math&amp;gt;. Das gleiche gilt für den Fehler in &amp;lt;math&amp;gt;c_{21}&amp;lt;/math&amp;gt;. Die Wahrscheinlichkeit '''q''', dass ''keiner'' der beiden Fehler gefunden wird, ist demzufolge: '''q''' = &amp;lt;math&amp;gt;\frac{1}{2}&amp;lt;/math&amp;gt;.&lt;br /&gt;
               &lt;br /&gt;
:(c) in der gleichen Zeile, z.B. &amp;lt;math&amp;gt;c_{11}&amp;lt;/math&amp;gt; und &amp;lt;math&amp;gt;c_{12}&amp;lt;/math&amp;gt;. Es gilt: Der Fehler wird gefunden, wenn &amp;lt;math&amp;gt;\delta_1 \ne \gamma_1 \Leftrightarrow \alpha_1*c_{11}+\alpha_2*c_{12}\ne 0&amp;lt;/math&amp;gt;. Hier treten nun zwei ungünstige Fälle auf: &lt;br /&gt;
::1) Der Fehler wird u.a. dann nicht gefunden, wenn &amp;lt;math&amp;gt;\alpha_1 = \alpha_2=0&amp;lt;/math&amp;gt;. Die Wahrscheinlichkeit dafür ist  wieder '''q'''=&amp;lt;math&amp;gt;\frac{1}{4}&amp;lt;/math&amp;gt;&lt;br /&gt;
::2) &amp;lt;math&amp;gt;\alpha_1=\alpha_2=1&amp;lt;/math&amp;gt; (dies geschieht ebenfalls mit Wahrscheinlichkeit &amp;lt;math&amp;gt;\frac{1}{4}&amp;lt;/math&amp;gt;), aber die Werte &amp;lt;math&amp;gt;c_{11}&amp;lt;/math&amp;gt; und &amp;lt;math&amp;gt;c_{12}&amp;lt;/math&amp;gt; sind &amp;quot;zufälligerweise&amp;quot; so falsch, dass sich die Fehler gegenseitig aufheben. Die Wahrscheinlichkeit, dass beide Bedingungen gelten, ist auf jeden Fall '''q''' =  &amp;lt;math&amp;gt;\epsilon&amp;lt;\frac{1}{4}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Analog behandelt man die Fälle, dass C drei oder vier Fehler enthält. Fasst man die Fälle zusammen, ergibt sich, dass die Wahrscheinlichkeit, einen vorhandenen Fehler '''nicht''' zu entdecken, sicher kleiner als &amp;lt;math&amp;gt;\frac{1}{2}&amp;lt;/math&amp;gt; ist. Dies gilt auch allgemein:&lt;br /&gt;
&lt;br /&gt;
;Satz:&lt;br /&gt;
*Die Wahrscheinlichkeit, dass Freivalds Algorithmus einen vorhandenen Fehler '''nicht''' findet, ist '''q''' &amp;lt; &amp;lt;math&amp;gt;\frac{1}{2}&amp;lt;/math&amp;gt;. Wir haben diesen Satz oben für N=2 bewiesen, ein vollständiger Beweis findet sich in der [http://en.wikipedia.org/wiki/Freivald's_algorithm#Error_Analysis Wikipedia].&lt;br /&gt;
 &lt;br /&gt;
;Folgerung: &lt;br /&gt;
*Lässt man Freivalds Algorithmus mit verschiedenen &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt; k-mal laufen, gilt &amp;lt;math&amp;gt;q_k &amp;lt; 2^{-k}&amp;lt;/math&amp;gt; für die Wahrscheinlichkeit, dass '''keiner''' der k Durchläufe einen vorhandenen Fehler findet. Diese Wahrscheinlichkeit konvergiert sehr schnell gegen 0. Das heißt, der Algorithmus findet mit beliebig hoher Wahrscheinlichkeit ein Gegenbeispiel zu H (falls es eins gibt), wenn man ihn nur genügend oft mit jeweils anderen Zufallszahlen wiederholt. Daraus folgt, dass Testen ein effektives Fehlersuchverfahren sein kann -- die oben erwähnte Einschränkung von Dijktra trifft zwar zu, aber Tests, die mit so hoher Wahrscheinlichkeit funktionieren, sind für die Praxis meistens vollkommen ausreichend.&lt;br /&gt;
&lt;br /&gt;
=== Vergleich formaler Korrektheitsbeweis und Testen ===&lt;br /&gt;
&lt;br /&gt;
Nachdem nun die formalen Methoden sowie der Software-Test vorgestellt worden sind, ist nun die Frage aufzugreifen, welcher der beiden Vorgänge der bessere ist. Allgemein gilt:&lt;br /&gt;
&lt;br /&gt;
;randomisierte Algorithmen&lt;br /&gt;
               &lt;br /&gt;
*sind schnell und einfach:&lt;br /&gt;
#da die Operationen einfach sind und wenig Zeit kosten&lt;br /&gt;
#des öfteren eine Auswahl vorgenommen wird ohne die Gesamtmenge näher zu betrachten&lt;br /&gt;
#die Auswahl selbst aufgrund einfacher Kriterien (bspw. zufällige Auswahl) erfolgt&lt;br /&gt;
*können Lösungen approximieren und liefern gute approximative Lösungen&lt;br /&gt;
&lt;br /&gt;
;formaler Korrektheitsbeweis mit deterministischen Algorithmen (siehe auch [http://de.wikipedia.org/wiki/Determinismus_(Algorithmus)])&lt;br /&gt;
  &lt;br /&gt;
*bei jedem Aufruf des Beweisers werden immer die selben Schritte durchlaufen&lt;br /&gt;
*keine Zufallswerte&lt;br /&gt;
*komplexer Aufbau&lt;br /&gt;
*oft sehr lange Laufzeit, z.B. mehrere Tage oder gar Monate&lt;br /&gt;
&lt;br /&gt;
Für die formalen Methoden spricht, dass man mit ihnen im Prinzip beweisen kann, dass H nun entweder tatsächlich falsch oder richtig ist. Die formalen Beweise bei realen Problemen sind allerdings so kompliziert, dass sie ebenfalls mit Computerhilfe erbracht werden müssen. Dadurch liegt auch hier keine 100%-ige Korrektheitsgarantie vor: Auch formale Methoden können zum falschen Ergebnis kommen, z.B. durch Hardwarefehler, Compilerbugs, oder unvorhergesehenes Umkippen von Bits (z.B. durch kosmische Strahlung -- diese Gefahr ist im Weltall sehr ernst zu nehmen). Die Möglichkeit von Hardwarefehlern wirkt sich auf die formalen Methoden wesentlich stärker aus, weil diese typischerweise wesentlich längere Laufzeiten haben als entsprechende Testalgorithmen. Es kann deshalb durchaus vorkommen, dass Tests eine höhere Erfolgswahrscheinlichkeit haben als ein formaler Beweis, wie die folgende Beispielrechnung zeigt. Wir nehmen an, dass die Hardware eine &amp;quot;Halbwertszeit&amp;quot; von 50 Millionen Sekunden hat, d.h. ein Hardwarefehler tritt im Durchschnitt etwa alle 20 Monate auf. Dann ist die Wahrscheinlichkeit, dass ein deterministischer Algorithmus '''nicht''' zum Ergebnis (oder zum falschen Ergebnis) kommt:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;math&amp;gt;q_{\mathrm{Beweis}} \approx 0.001&amp;lt;/math&amp;gt;, falls der Beweisalgorithmus 1 Tag benötigt,&lt;br /&gt;
* &amp;lt;math&amp;gt;q_{\mathrm{Beweis}} \approx 0.01&amp;lt;/math&amp;gt;, falls der Beweisalgorithmus 1 Woche benötigt,&lt;br /&gt;
* &amp;lt;math&amp;gt;q_{\mathrm{Beweis}} \approx 0.035&amp;lt;/math&amp;gt;, falls der Beweisalgorithmus 1 Monat benötigt.&lt;br /&gt;
&lt;br /&gt;
Zum Vergleich nehmen wir an, dass der entsprechende Softwaretest einmal pro Sekunde ausgeführt werden kann, und dass jeder Durchlauf den Fehler mit einer Wahrscheinlichkeit von &amp;lt;math&amp;gt;\frac{1}{2}&amp;lt;/math&amp;gt; '''nicht''' findet. Unter gleichzeitiger Berücksichtigung der Wahrscheinlichkeit von Hardwarefehlern gilt dann&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;math&amp;gt;q_{\mathrm{Test}} \approx 0.5&amp;lt;/math&amp;gt;, falls der Test 1-mal wiederholt wird,&lt;br /&gt;
* &amp;lt;math&amp;gt;q_{\mathrm{Test}} \approx 0.001&amp;lt;/math&amp;gt;, falls der Test 10-mal wiederholt wird,&lt;br /&gt;
* &amp;lt;math&amp;gt;q_{\mathrm{Test}} \approx 10^{-6}&amp;lt;/math&amp;gt;, falls der Test 100-mal wiederholt wird.&lt;br /&gt;
&lt;br /&gt;
Mit anderen Worten: hier ist das Testen vorzuziehen, weil es unter realistischen Bedingungen eine höhere Erfolgswahrscheinlichkeit hat als der formale Beweis. Leider gibt es bisher keine Theorie, mit deren Hilfe man für ein gegebenes Problem systematisch Tests konstruieren kann, deren Misserfolgswahrscheinlichkeit bei wiederholter Anwendung garantiert so schnell gegen Null konvergiert wie die des Freivalds Algorithmus. Dies ist ein offenes Problem der Informatik.&lt;br /&gt;
&lt;br /&gt;
==Anwendung des Softwaretestverfahren==&lt;br /&gt;
===Beispiel an Python-Code===&lt;br /&gt;
&lt;br /&gt;
Man betrachte die Aufgabe, aus einer Zahl x die Wurzel zu ziehen. Dies kann man erreichen, indem man mit Hilfe des Newtonschen Iterationsverfahrens eine Nullstelle des Polynoms &lt;br /&gt;
:&amp;lt;math&amp;gt;f(y) = x - y^2 = 0&amp;lt;/math&amp;gt; &lt;br /&gt;
sucht. Ist eine Näherungslösung &amp;lt;math&amp;gt;y^{(t)}&amp;lt;/math&amp;gt; bekannt, erhält man eine bessere Näherung durch&lt;br /&gt;
:&amp;lt;math&amp;gt;y^{(t+1)} = y^{(t)} - \frac{f(y^{(t)})}{f'(y^{(t)})}&amp;lt;/math&amp;gt;.&lt;br /&gt;
Mit &amp;lt;math&amp;gt;f\,'(y) = -2y&amp;lt;/math&amp;gt; wird das zu&lt;br /&gt;
:&amp;lt;math&amp;gt;y^{(t+1)} = y^{(t)} + \frac{x-(y^{(t)})^2}{2y^{(t)}}=\frac{y^{(t)}+x/y^{(t)}}{2}&amp;lt;/math&amp;gt;. &lt;br /&gt;
Im Spezialfall des Wurzelziehens war diese Newton-Iteration übrigens bereits im Altertum als [http://en.wikipedia.org/wiki/Babylonian_method#Babylonian_method Babylonische Methode] bekannt. Man kann dieselbe durch das folgende (allerding noch nicht korrekte) Pythonprogramm realisieren:&lt;br /&gt;
&lt;br /&gt;
           1   def sqrt(x):&lt;br /&gt;
           2       if (x&amp;lt;0):&lt;br /&gt;
           3           raise ValueError(&amp;quot;sqrt of negative number&amp;quot;)&lt;br /&gt;
           4       y = x / 2&lt;br /&gt;
           5       while y*y != x:&lt;br /&gt;
           6           y =(y + x/y) / 2&lt;br /&gt;
           7       return y:&lt;br /&gt;
&lt;br /&gt;
Für den oben aufgeführten Pythoncode können Tests mit Hilfe des Python-Moduls &amp;quot;[http://docs.python.org/lib/module-unittest.html unittest]&amp;quot; geschrieben werden (siehe auch Übungsaufgaben). Wir erklären hier die wichtigsten Befehle aus diesem Modul. Wir implementieren eine Testfunktionen (diese muss, wie im Python-Handbuch beschrieben, Methode einer Testklasse sein).&lt;br /&gt;
&lt;br /&gt;
   class SqrtTest(unittest.TestCase):&lt;br /&gt;
     def testsqrt(self): &lt;br /&gt;
         ...&lt;br /&gt;
&lt;br /&gt;
Zunächst muss man prüfen, ob die Vorbedingung korrekt getestet wird, d.h. ob bei einer negativen Zahl x eine Exception ausgelöst wird; dafür benötigt man &lt;br /&gt;
&lt;br /&gt;
         self.assertRaises(ValueError, sqrt, -1) &lt;br /&gt;
Sollte keine Exception vom Type &amp;lt;tt&amp;gt;ValueError&amp;lt;/tt&amp;gt; ausgelöst werden, dann würde der Test hier einen Fehler signalisieren. Dieser Test funktioniert aber.&lt;br /&gt;
&lt;br /&gt;
Weiter testen wir einige Beispiele, deren Wurzel wir kennen:&lt;br /&gt;
&lt;br /&gt;
         self.assertEqual(sqrt(9),3) &lt;br /&gt;
Wäre hier das Ergebnis ungleich 3, würde ebenfalls ein Fehler signalisiert, aber es funktioniert in unserem Falle. Der Test&lt;br /&gt;
&lt;br /&gt;
         self.assertEqual(sqrt(1),1)&lt;br /&gt;
schlägt jedoch mit &amp;lt;tt&amp;gt;ZeroDivisionError&amp;lt;/tt&amp;gt; fehl! Wir sehen, dass in Zeile 4 eine Ganzzahldivision durchgeführt wird, deren Ergebnis stets abgerundet wird, was hier zu &amp;lt;tt&amp;gt;y = 0&amp;lt;/tt&amp;gt; und damit zum Fehler in Zeile 6 führt. Wieso hat dann aber der erste Test &amp;lt;tt&amp;gt;sqrt(9) == 3&amp;lt;/tt&amp;gt; funktioniert? Hier gilt &amp;lt;tt&amp;gt;x / 2 == 4&amp;lt;/tt&amp;gt; und &amp;lt;tt&amp;gt;x / y == 2&amp;lt;/tt&amp;gt; (jeweils nach Abrunden), und der Mittelwert der beiden Schätzungen ist gerade &amp;lt;tt&amp;gt;y == 3&amp;lt;/tt&amp;gt;, also zufällig das richtige Ergebnis. Allgemein sehen wir jedoch, dass es nicht korrekt ist, mit ganzen Zahlen zu rechnen. Wir müssen also den Input zunächst in einen Gleitkommawert umwandeln:&lt;br /&gt;
&lt;br /&gt;
           1   def sqrt(x):&lt;br /&gt;
           1a      x = float(x)&lt;br /&gt;
           2       if (x&amp;lt;0):&lt;br /&gt;
           3           raise ValueError(&amp;quot;sqrt of negative number&amp;quot;)&lt;br /&gt;
           4       y = x / 2&lt;br /&gt;
           5       while y*y != x:&lt;br /&gt;
           6           y =(y + x/y) / 2&lt;br /&gt;
           7       return y:&lt;br /&gt;
&lt;br /&gt;
Jetzt funktionieren die vorhandenen Tests, aber bei anderen Zahlen (z.B. &amp;lt;tt&amp;gt;x = 1.21&amp;lt;/tt&amp;gt;) läuft das Programm in eine Endlosschleife. Dies liegt daran, dass durch die beschränkte Genauigkeit der Gleitkomma-Darstellung selten exakte Gleichheit in der &amp;lt;tt&amp;gt;while&amp;lt;/tt&amp;gt;-Bedingung erreicht wird. Man darf nicht auf Gleichheit prüfen, sondern muss den relativen Fehler beschränken:&lt;br /&gt;
&lt;br /&gt;
           1   def sqrt(x):&lt;br /&gt;
           1a      x = float(x)&lt;br /&gt;
           2       if (x&amp;lt;0):&lt;br /&gt;
           3           raise ValueError(&amp;quot;sqrt of negative number&amp;quot;)&lt;br /&gt;
           4       y = x / 2&lt;br /&gt;
           5       while abs(1.0 - x / y**2) &amp;gt; 1e-15:  # check for relative difference&lt;br /&gt;
           6           y =(y + x/y) / 2&lt;br /&gt;
           7       return y:&lt;br /&gt;
&lt;br /&gt;
Jetzt terminiert das Programm, aber der Test&lt;br /&gt;
&lt;br /&gt;
        self.assertEqual(sqrt(1.21)**2, 1.21)  # schlägt fehl&lt;br /&gt;
&lt;br /&gt;
schlägt wegen der beschränkten Genauigkeit der Gleitkommadarstellung fehl. Man umgeht dieses Problem, indem man im Tests selbst nur nähreungsweise Gleichheit fordert, z.B. auf 15 Dezimalstellen genau (bei 16 Dezimalen würde es nicht mehr funktionieren):&lt;br /&gt;
&lt;br /&gt;
        self.assertAlmostEqual(sqrt(1.21)**2, 1.21, 15)&lt;br /&gt;
&lt;br /&gt;
Wenden wir jetzt das ''Prinzip der Condition Coverage'' an (siehe unten), sehen wir, dass die &amp;lt;tt&amp;gt;while&amp;lt;/tt&amp;gt;-Bedingung bei allen bisherigen Tests zunächst mindestens einmal &amp;lt;tt&amp;gt;true&amp;lt;/tt&amp;gt; gewesen ist. Ein weiterer sinnvoller Tests ist deshalb einer, der diese Bedingung sofort &amp;lt;tt&amp;gt;false&amp;lt;/tt&amp;gt; macht. Dies trifft z.B. bei &amp;lt;tt&amp;gt;x == 4&amp;lt;/tt&amp;gt; zu, weil &amp;lt;tt&amp;gt;y = x / 2&amp;lt;/tt&amp;gt; hier gerade die korrekte Wurzel liefert. Wir fügen deshalb den Test&lt;br /&gt;
&lt;br /&gt;
         self.assertEqual(sqrt(4), 2) &lt;br /&gt;
&lt;br /&gt;
hinzu, der erfolgreich verläuft. Das ''Prinzip der Domänen-Zerlegung'' (siehe unten) führt uns weiter dazu, die Wurzel aus Null als sinnvollen Test zu betrachten, weil die Null am Rand des erlaubten Wertebereichs liegt. Der Test&lt;br /&gt;
&lt;br /&gt;
        self.assertEqual(sqrt(0), 0)  # schlägt fehl&lt;br /&gt;
&lt;br /&gt;
schlägt in der Tat mit einem &amp;lt;tt&amp;gt;ZeroDivisionError&amp;lt;/tt&amp;gt; fehl: In der Abfrage der &amp;lt;tt&amp;gt;while&amp;lt;/tt&amp;gt;-Bedingung wird jetzt durch &amp;lt;tt&amp;gt;y == 0&amp;lt;/tt&amp;gt; geteilt. Wir können diesen Fehler beheben, indem wir die Division aus der Bedingung eliminieren:&lt;br /&gt;
&lt;br /&gt;
           1   def sqrt(x):&lt;br /&gt;
           1a      x = float(x)&lt;br /&gt;
           2       if (x&amp;lt;0):&lt;br /&gt;
           3           raise ValueError(&amp;quot;sqrt of negative number&amp;quot;)&lt;br /&gt;
           4       y = x / 2&lt;br /&gt;
           5       while abs(y**2 - x) &amp;gt; 1e-15*x:  # check for relative difference without division&lt;br /&gt;
           6           y =(y + x/y) / 2&lt;br /&gt;
           7       return y:&lt;br /&gt;
&lt;br /&gt;
Damit ist auch dieses Problem behoben. Wir sehen also, wie das systematische Testen uns dabei hilft, Fehler im Programm zu finden und zu eliminieren.&lt;br /&gt;
&lt;br /&gt;
===Definition guter Tests===&lt;br /&gt;
&lt;br /&gt;
Wir haben gezeigt, dass Testen eine effektive Methode ist, um Fehler in Algorithmen zu finden. Allerdings gilt das nur, wenn Tests und Testdaten geschickt gewählt werden. Wir zeigen bewährte Methoden dafür. &lt;br /&gt;
&lt;br /&gt;
====Generieren von Referenzdaten====&lt;br /&gt;
&lt;br /&gt;
Wie immer man die Tests definiert hat, muss man am Ende die Ausgabe des Algorithmus mit dem korrekten Ergebnis vergleichen. Man bezeichnet ein bekanntes korrektes Ergebnis als ''Referenz-Ergebnis''. Dieses muss man aber erst einmal kennen, was sich mitunter als schwierig erweist. Folgende Verfahren haben sich als zweckmäßig erwiesen:&lt;br /&gt;
* Bei bestimmten Eingaben ist das Ergebnis für den Menschen einfach zu bestimmen, für den Algorithmus ist diese Eingabe aber ebenso schwierig wie jede andere. Dies gilt zum Beispiel für die Quadratzahlen im obigen Beispiel: der Algorithmus kennt keine Quadratzahlen und behandelt sie wie jede andere reelle Zahl. Deshalb eignen sich die Quadratzahlen zum Testen. Auch beim Sortieren kleiner Listen kann die korrekte Sortierung leicht bestimmt und als Referenz-Ergebnis abgespeichert werden. Der Test vergleicht dann einfach die Ausgabe des Sortieralgorithmus mit dem Referenz-Ergebnis.&lt;br /&gt;
* Oft kann man das korrekte Ergenis mit einem alternativen Verfahren berechnen. Dies gilt insbesondere, wenn man einen effizienten, aber komplizierten Algorithmus testen will. Dann berechnet man die Referenz-Ergebnisse mit einem langsamen, aber einfachen Verfahren. Dies ist möglich, weil man die Referenz-Ergebnisse ja abspeichern kann und der langsame Algorithmus daher nur wenige Male benutzt werden muss. Beispielsweise kann man einen komplizierten Sortieralgorithmus (Quicksort) mit Hilfe von selection sort testen.&lt;br /&gt;
* In vielen Fällen steht ein alternatives Programm zur Verfügung, z.B. eine ältere Version des zu testenden Programms, oder ein kommerzielles Programm (bzw. eine Demoversion), das dasselbe Problem löst, aber im aktuellen Kontext nicht verwendet werden kann (weil es z.B. zu teuer ist, oder nur auf einem Mac läuft). Diese Methode bietet sich auch an, wenn man einen Algorithmus aus einer Programmiersprache in eine andere portieren muss. &lt;br /&gt;
* Manchmal kann das korrekte Ergebnis nicht direkt angegeben werden, aber man kennt bestimmte Eigenschaften. Beim Sortieren kann man z.B. testen, dass kein Element des sortierten Arrays größer ist als das darauffolgende. Man testes also die Nachbedingungen. Eine abgeschwächte Versionen dieser Methode wird für randomisierte Algorithmen verwendet: Ist die Wahrscheinlichkeitsverteilung der Testeingaben bekannt, kann man die Wahrscheinlichkeitsverteilung der Ergebnisse, oder zumindest wichtige Eigenschaften wie z.B. den Mittelwert, mathematisch vorhersagen. Der Test ermittelt dann, ob die Ausgaben über viele Durchläufe des Algorithmus diese statistischen Eigenschaften aufweisen.&lt;br /&gt;
&lt;br /&gt;
====Arten von Tests====&lt;br /&gt;
&lt;br /&gt;
Man unterscheidet 3 grundlegende Arten von Tests:&lt;br /&gt;
&lt;br /&gt;
;Black-box Tests [http://en.wikipedia.org/wiki/Black_box_testing]: Hier ist dem Tester nur die Spezifikation, aber nicht die Implementation des Algorithmus bekannt. Alle Tests sowie die Eingaben und Referenz-Ergebnisse müssen aus der Spezifikation abgeleitet werden. Die automatisierte Generierung guter Tests aus der Spezifikation ist ein aktives Forschungsgebiet.&lt;br /&gt;
;Gray-box Tests (auch Glass-box Tests) [http://www.cse.fau.edu/~maria/COURSES/CEN4010-SE/C13/glass.htm]: Hier kennt der Tester auch die Implementation und kann dadurch Tests entwerfen, die für diese spezielle Implementation besonders aussagekräftig sind. Es besteht allerdings die Gefahr, dass der Tester nicht mehr unvoreingenommen an das Testproblem herangeht, und Zustände, die seiner Meinung nach gar nicht vorkommen können, auch nicht testet (erst später stellt sich heraus, dass diese Zustände doch vorkommen).&lt;br /&gt;
;White-box Tests [http://en.wikipedia.org/wiki/White_box_testing]: Hier kann der Tester die Implementation sogar in geeigneter Weise verändern, z.B. &lt;br /&gt;
:* explizite Tests für Vor- und Nachbedingungen (&amp;quot;Assertions&amp;quot;) einbauen. Dies bietet sich insbesondere in der alpha- und beta-Testphase eines Programms an, um Fehler schnell zu lokalisieren. Auch die unter Windows bekannte Dialogbox &amp;quot;Diesen Fehler bitte auch an Microsoft melden&amp;quot; wird durch solche eingebauten Assertions ausgelöst, wenn das Programm in einen illegalen Zustand geraten ist und abgebrochen werden muss.&lt;br /&gt;
:* zusätzlichen Code einbauen, der feststellt, ob alle Teile des Programms auch tatsächlich getestet wurden (&amp;quot;[http://blogs.msdn.com/phuene/archive/2007/05/03/code-coverage-instrumentation.aspx code coverage instrumentation]&amp;quot;). Dieser Code gibt nach dem Testen z.B. aus, welche Programmzeilen von keinem existierenden Test aufgerufen worden sind. Wenn der ausgeführte Code sehr stark von den Daten abhängt (z.B. bei interaktiven Programmen), kann es sehr schwierig sein, die ''coverage'' auf andere Weise festzustellen.&lt;br /&gt;
:* absichtlich Bugs einbauen (die automatisch wieder abgeschaltet werden, wenn das Testen vorbei ist). Durch diese &amp;quot;[http://en.wikipedia.org/wiki/Fault_injection fault injection]&amp;quot; kann man herausfinden, ob die Tests mächtig genug sind, vorhandene Bugs zu finden.&lt;br /&gt;
&lt;br /&gt;
====Prinzipien für die Generierung von Testdaten====&lt;br /&gt;
&lt;br /&gt;
;Prinzip der Regressionstests (&amp;quot;[http://en.wikipedia.org/wiki/Regression_testing Regression testing]&amp;quot;): Häufig werden Tests während der Programmentwicklung verwendet, um einen Algorithmus zu debuggen. Sobald der Algorithmus aber funktioniert werden die Tests gelöscht, denn sie werden ja jetzt nicht mehr gebraucht. Dies ist ein schwerwiegender ''Fehler'': Jedes erfolgreiche Programm muss früher oder später weiterentwickelt werden (zumindest die Anpassung an eine neue Betriebssystemversion ist ab und zu notwendig). Jede Änderung birgt aber die Gefahr, dass sich neue Bugs in bisher funktionierenden Code einschleichen. Man sollte deshalb alle Tests aufheben und in einer ''test suite'' sammeln. Durch diese &amp;quot;regression tests&amp;quot; kann man nach jeder Änderung feststellen, ob die alte Funktionalität noch intakt ist, und gegebenenfalls die letzte Änderung einfach rückgängig machen. Tut man dies nicht, kann die Gefahr von unbeabsichtigten destruktiven Änderungen so groß werden, dass das Programm gar nicht mehr weiterentwickelt werden kann. Dies wird drastisch durch den bekannten Spruch &amp;quot;never change a running program&amp;quot; ausgedrückt.&lt;br /&gt;
&lt;br /&gt;
;Prinzip der äquivalenten Eingaben (Domain Partitioning oder Equivalence Partitioning) [http://en.wikipedia.org/wiki/Equivalence_partitioning]: Für ähnliche Eingaben verhält sich ein Algorithmus normalerweise ähnlich, und es hat keinen Sinn, alle diese Eingaben zu testen. Statt dessen teilt (partitioniert) man die Eingabedomäne in Äquivalenzklassen, die vom Algorithmus im wesentlichen gleich behandelt werden. Im obigen Beispiel der Wurzelberechnung ergeben sich zwei Klassen aus der Spezifikation: die negativen Zahlen (für die die Wurzel undefiniert ist und deshalb ein Fehler signalisiert werden muss) und die nicht-negativen Zahlen. Wenn man auch den Quellcode kennt (gray-box testing), kann man die Eingaben oft feiner unterteilen. Z.B. werden häufig unterschiedliche Algorithmen für kleine und für große Eingaben benutzt. Viele Quicksort-Implementationen verwenden beispielsweise für Arrays mit höchstens vier Elementen ein explizites Sortierverfahren, für Arrays der Länge 5 bis 25 selection sort, und erst für größere Arrays das eigentliche Quicksort. Aus der Einteilung der Eingabedomäne ergeben sich zwei wichtige Regeln für die Wahl der Testdaten:&lt;br /&gt;
:* Aus jeder Äquivelenzklasse wählt man mindestens einen typischen Vertreter, um das normale Verhalten des Algorithmus in jedem Fall zu testen.&lt;br /&gt;
:* Aus jeder Äquivelenzklasse wählt man Randwerte, weil gerade bei diesen Werten am häufigsten Fehler gemacht werden. Im obigen Wurzelbeispiel ist der Randwert die Null, die in der Tat in einer Version des Algorithmus zu einem &amp;lt;TT&amp;gt;ZeroDivisionError&amp;lt;/tt&amp;gt; geführt hat. Andere typische Randfehler sind, dass Randelemente dem falschen Algorithmenzweig zugeordnet werden (z.B. wenn bei unserem Wurzelbeispiel die Abfrage am Anfang &amp;lt;tt&amp;gt;if x &amp;lt;= 0:&amp;lt;/tt&amp;gt; statt &amp;lt;tt&amp;gt;if x &amp;lt; 0:&amp;lt;/tt&amp;gt; gewesen wäre), dass Schleifen um einen Index zu spät beginnen oder zu früh abbrechen (&amp;quot;[http://en.wikipedia.org/wiki/Off-by-one_error Off-by-one errors]&amp;quot;), oder dass ein seltener Randfall gar nicht implementiert ist und einfach zum Absturz führt.&lt;br /&gt;
&lt;br /&gt;
;Prinzip, den Fehler zu reproduzieren (Failure Reproduction): Wenn ein Bug gemeldet wird, welches die Tests bisher übersehen haben, fügt man einen Test hinzu, der dieses Bug findet. Im Zusammenhang mit regression tests ist damit sichergestellt, dass dasselbe Bug nicht noch einmal auftreten kann.&lt;br /&gt;
&lt;br /&gt;
;Prinzip der Code Coverage [http://en.wikipedia.org/wiki/Code_coverage]: Hier stellt man sicher, dass tatsächlich der gesamte Code (oder ein vorher festgelegter hoher Prozentsatz) getestet wurde. Gerade bei komplizierten interaktiven Programmen ist diese &amp;quot;code coverage&amp;quot; mitunter nicht leicht zu erreichen, weil manche Programmteile nur bei sehr seltenen oder obskuren Eingaben ausgeführt werden. Eine minimale code coverage erreicht man allerdings bereits, wenn man in einem black-box-Test die Testdaten nach dem Prinzip der äquivalenten Eingaben auswählt, weil dann aus jeder Äquivalenzklasse mindestens ein Vertreter getestet wird. Im Allgemeinen muss man aber den Quellcode zumindest kennen (gray-box-Test), um geeignete Testdaten für code coverage zu identifizieren. Code coverage kann in verschiednen Graden angestrebt werden&lt;br /&gt;
:* Function coverage: Jede Funktion eines Programms sollte mindestens einmal aufgerufen werden.&lt;br /&gt;
:* Statement coverage: Jedes Statement (d.h. im wesentlichen jede Programmzeile) sollte mindestens einmal ausgeführt werden. Im obigen Wurzelbeispiel erfordert dies, dass z.B. mindestens einmal eine negative Zahl getestet wird, um die Exception zu prüfen.&lt;br /&gt;
:* Condition coverage: Jede Bedingung (explizit in &amp;lt;tt&amp;gt;if&amp;lt;/tt&amp;gt;-Bedingungen, implizit in den Abbruchbedingungen von &amp;lt;tt&amp;gt;for&amp;lt;/tt&amp;gt;- und &amp;lt;tt&amp;gt;while&amp;lt;/tt&amp;gt;-Schleifen) sollte mindestens einmal mit dem Ergebnis &amp;lt;tt&amp;gt;True&amp;lt;/tt&amp;gt; und einmal mit dem Ergebnis &amp;lt;tt&amp;gt;False&amp;lt;/tt&amp;gt; durchlaufen werden. Im Wurzelbeispiel haben wir die Eingabe &amp;lt;tt&amp;gt;x = 4&amp;lt;/tt&amp;gt; gewählt, damit die &amp;lt;tt&amp;gt;while&amp;lt;/tt&amp;gt;-Schleife auch einmal beim ersten Aufruf sofort &amp;lt;tt&amp;gt;False&amp;lt;/tt&amp;gt; liefert.&lt;br /&gt;
:* Path coverage: Jeder Programmpfad (d.h. jede Kombination von Wahrheitswerten bei allen Bedingungen) sollte einmal ausgeführt werden. Dies ist im Allgemeinen unerreichbar, weil es unendlich viele, oder zumindest zu viele verschiedene Pfade gibt.&lt;br /&gt;
:Die Qualität der Tests steigt, wenn eine hohe Coverage (am besten 100%) erreicht wird, und/oder man eine mächtigere Art von Coverage fordert.&lt;br /&gt;
&lt;br /&gt;
;Prinzip der erschöpfenden Tests: Wenn ein Algorithmus nur wenige mögliche Eingaben hat, kann man sämtliche Eingaben testen. Bei sehr wichtigen Algorithmen kann das auch dann noch sinnvoll sein, wenn es relativ viele mögliche Eingaben gibt. In den meisten Fällen ist es jedoch zu aufwändig.&lt;br /&gt;
&lt;br /&gt;
;Prinzip der vollständigen Paarung (Pair-wise coverage) [http://citeseer.ist.psu.edu/78354.html]: Wenn ein Algorithmus N Eingabeparameter hat, und jeder Parameter hat K&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; mögliche Werte, müssen bei der erschöpfenden Suche K&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;*...*K&amp;lt;sub&amp;gt;N&amp;lt;/sub&amp;gt; Kombinationen getestet werden. Beschränkt man sich in jedem Parameter auf typische Werte und Randwerte jeder Äquivalenzklasse, kann man K&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; zwar drastisch reduzieren, aber das Produkt K&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;*...*K&amp;lt;sub&amp;gt;N&amp;lt;/sub&amp;gt; wird immer noch sehr groß (bei 4 Parametern und nur 3 möglichen Werten pro Parameter hat man bereits 3&amp;lt;sup&amp;gt;4&amp;lt;/sup&amp;gt;=81 mögliche Kombinationen). Sei v&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt; der j-te Wert des Parameters i. Anstatt zu versuchen, alle Kombinationen zu testen, kann man fordern, dass zumindest alle möglichen Paare v&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt; und v&amp;lt;sub&amp;gt;mj&amp;lt;/sub&amp;gt; (i&amp;amp;ne;m) in mindestens einem Test vorkommen. Gibt es nur zwei Parameter, gewinnt man durch diese Einschränkung natürlich nichts, denn man muss mindestens K&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;*K&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; Tests durchführen. Hat man jedoch 3 Parameter, kann man mit weniger Tests auskommen als zuvor, da jeder Test bis zu drei verschiedene Paarungen abdecken kann (eine für den ersten und zweiten Parameter, eine für den ersten und dritten, eine für den zweiten und dritten). Bei vier Parametern werden sogar sechs Paarungen pro Test abgearbeitet usw. Die Theorie des &amp;quot;experimental design&amp;quot; beschreibt nun, wie man systematisch alle möglichen Paarungen mit möglichst wenigen Tests erzeugt. Es stellt sich heraus, dass man alle Paarungen von 3, 4 oder mehr Parametern oft mit genauso vielen Tests erzeugen kann wie bei 2 Parametern nötig wären. Dazu verwendet man die Methode der [http://en.wikipedia.org/wiki/Latin_square Latin Squares].  Wir beschreiben diese Methode für den einfachen Fall von 3 möglichen Werten pro Parameter.&lt;br /&gt;
&lt;br /&gt;
:Ein Latin Square der Größe 3 ist eine 3x3 Matrix, deren Einträge die Zahlen 1...3 sind, und zwar so, dass jede Zahl genau einmal in jeder Zeile und Spalte vorkommt (ähnlich wie beim Sudoku). Eine mögliche Matrix ist z.B.&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;P=\begin{pmatrix}1 &amp;amp; 2 &amp;amp; 3 \\&lt;br /&gt;
                      2 &amp;amp; 3 &amp;amp; 1 \\&lt;br /&gt;
                      3 &amp;amp; 1 &amp;amp; 2\end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
:Man bildet jetzt 9 Kombinationen der Zahlen 1...3, indem man zeilenweise durch die Matrix P geht, und den Zeilenindex (die Nummer der aktuellen Zeile) als erste Zahl, den Spaltenindex als zweite Zahl, und den Eintrag an der aktuallen Position als dritte Zahl verwendet. Man erhält&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;0&amp;quot; cellpadding=&amp;quot;7&amp;quot; align=&amp;quot;center&amp;quot;&lt;br /&gt;
|-align=&amp;quot;center&amp;quot; &lt;br /&gt;
|&lt;br /&gt;
! Komb. 1&lt;br /&gt;
! Komb. 2&lt;br /&gt;
! Komb. 3&lt;br /&gt;
! Komb. 4&lt;br /&gt;
! Komb. 5&lt;br /&gt;
! Komb. 6&lt;br /&gt;
! Komb. 7&lt;br /&gt;
! Komb. 8&lt;br /&gt;
! Komb. 9&lt;br /&gt;
|-&lt;br /&gt;
!Zahl 1 (Zeilenindex)&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|-&lt;br /&gt;
! Zahl 2 (Spaltenindex)&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|-&lt;br /&gt;
! Zahl 3 (aktueller Matrixeintrag von P)&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
:Diese Tabelle bestimmt, welcher Wert in jedem Test für jeden Parameter verwendet wird. Z.B. wird der erste Test mit v&amp;lt;sub&amp;gt;11&amp;lt;/sub&amp;gt; (erster Wert des ersten Parameters), v&amp;lt;sub&amp;gt;21&amp;lt;/sub&amp;gt; (erster Wert des zweiten Parameters), v&amp;lt;sub&amp;gt;31&amp;lt;/sub&amp;gt; (erster Wert des dritten Parameters) aufgerufen&lt;br /&gt;
       assertEqual( foo(v11, v21, v31), foo_reference1)&lt;br /&gt;
(reference1 ist das korrekte Referenz-Ergebnis für diese Parameterbelegung). Der letzte Test hat die Parameter v&amp;lt;sub&amp;gt;13&amp;lt;/sub&amp;gt;, v&amp;lt;sub&amp;gt;23&amp;lt;/sub&amp;gt;, v&amp;lt;sub&amp;gt;32&amp;lt;/sub&amp;gt;&lt;br /&gt;
       assertEqual( foo(v13, v23, v32), foo_reference9)&lt;br /&gt;
:Man überzeugt sich leicht, dass diese 9 Tests jede mögliche Paarung genau einmal enthalten. Hat der Algorithmus 4 Parameter, benötigt man einen zweiten Latin Square, der zum ersten orthogonal ist. Zwei Latin Squares P und Q heißen orthogonal, wenn alle Paare c&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt;=(P&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt;, Q&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt;) eindeutig sind, d.h. es gilt c&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt;&amp;amp;ne;c&amp;lt;sub&amp;gt;kl&amp;lt;/sub&amp;gt; falls i&amp;amp;ne;k und j&amp;amp;ne;l. Ein zu dem obigen P orthogonales Q ist z.B.&lt;br /&gt;
:&amp;lt;math&amp;gt;Q=\begin{pmatrix}1 &amp;amp; 2 &amp;amp; 3 \\&lt;br /&gt;
                        3 &amp;amp; 1 &amp;amp; 2 \\&lt;br /&gt;
                        2 &amp;amp; 3 &amp;amp; 1\end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
: Jetzt bildet man Kombinationen aus 4 Zahlen, indem man zur obigen Tabelle noch eine vierte Zeile hinzufügt, die die aktuellen Einträge von Q für den jeweiligen Zeilen- und Spaltenindex enthält:&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;0&amp;quot; cellpadding=&amp;quot;7&amp;quot; align=&amp;quot;center&amp;quot;&lt;br /&gt;
|-align=&amp;quot;center&amp;quot; &lt;br /&gt;
|&lt;br /&gt;
! Komb. 1&lt;br /&gt;
! Komb. 2&lt;br /&gt;
! Komb. 3&lt;br /&gt;
! Komb. 4&lt;br /&gt;
! Komb. 5&lt;br /&gt;
! Komb. 6&lt;br /&gt;
! Komb. 7&lt;br /&gt;
! Komb. 8&lt;br /&gt;
! Komb. 9&lt;br /&gt;
|-&lt;br /&gt;
!Zahl 1 (Zeilenindex)&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|-&lt;br /&gt;
! Zahl 2 (Spaltenindex)&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|-&lt;br /&gt;
! Zahl 3 (aktueller Matrixeintrag von P)&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|-&lt;br /&gt;
! Zahl 4 (aktueller Matrixeintrag von Q)&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
:Es sind immer noch nur 9 Tests nötig, um alle Paarungen zu erzeugen. Der erste und letzte Test sind nun:&lt;br /&gt;
       assertEqual( bar(v11, v21, v31, v41), bar_reference1)&lt;br /&gt;
       ...&lt;br /&gt;
       assertEqual( bar(v13, v23, v32, v41), bar_reference9)&lt;br /&gt;
:Die Methode der Latin Squares  funktioniert auch, wenn mehr als 3 Belegungen für jeden Parameter möglich sind, und wenn es mehr als 4 Parameter gibt. Für die Einzelheiten verweisen wir auf die Literatur, z.B. [http://citeseer.ist.psu.edu/78354.html], [http://en.wikipedia.org/wiki/Latin_square]. Empirische Untersuchungen haben ergeben, dass die Methode der vollständigen Paarung oft über 90% der Fehler in einem Programm finden kann.&lt;br /&gt;
&lt;br /&gt;
[[Effizienz|Nächstes Thema]]&lt;/div&gt;</summary>
		<author><name>Jschleic</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Korrektheit&amp;diff=4726</id>
		<title>Korrektheit</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Korrektheit&amp;diff=4726"/>
				<updated>2010-08-10T11:05:11Z</updated>
		
		<summary type="html">&lt;p&gt;Jschleic: /* Vergleich formaler Korrektheitsbeweis und Testen */ Satzbau + typo&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Man unterscheidet zwischen Prüfung der Korrektheit (Verifikation) und Prüfung der Spezifikation (Validierung). Ein Algorithmus heißt korrekt, wenn er sich gemäß seiner Spezifikation verhält, auch wenn seine Spezifikation nicht immer die gewünschten Ergebnisse liefert. Die Spezifikation beschreibt die Vorbedingungen (was vor der Anwendung des Algorithmus gilt, so dass der Algorithmus überhaupt angewendet werden darf) und die Nachbedingungen (was nach der Anwendung des Algorithmus gilt, welchen Zustand des Systems der Algorithmus also erzeugt). Hier geht es ausschliesslich um die Prüfung der Korrektheit eines Algorithmus, also darum, ob die spezifizierten Nachbedingungen wirklich gelten.&lt;br /&gt;
 &lt;br /&gt;
Nebenbemerkungen&lt;br /&gt;
# es gibt Algorithmen, die ''nie'' mit einer 100-prozentigen Wahrscheinlichkeit richtige Ergebnisse liefern können (z.B. [http://en.wikipedia.org/wiki/Primality_test#Probabilistic_tests nichtdeterministische Primzahltests]). &lt;br /&gt;
# '''Korrektheit''' wird in Algorithmenbüchern meist nur im Zusammenhang mit konkreten Algorithmen behandelt, aber nicht als übergreifendes Problem. Dies erscheint der Bedeutung von Korrektheit nicht angemessen.&lt;br /&gt;
&lt;br /&gt;
Will man die Korrektheit eines Algorithmus/Programms feststellen, hat man 3 Vorgehensweisen zur Verfügung: Prüfung der syntaktischen Korrektheit, formaler Korrektheitsbeweis und Softwaretest.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Syntaktische Korrektheit ==&lt;br /&gt;
&lt;br /&gt;
Die syntaktische Korrektheit behandeln wir hier nur kurz und der Vollständigkeit halber. Sie wird in den Veranstaltungen zur theoretischen Informatik (Grammatiken) und zum Compilerbau ausführlich behandelt.&lt;br /&gt;
&lt;br /&gt;
=== Syntaktische Prüfung ===&lt;br /&gt;
Es wird eine Grammatik definiert, deren Regeln die Implementation des Algorithmus befolgen muss. Für ein Programm heißt das beispielsweise, dass die Syntax der Programmiersprache eingehalten werden muss.&lt;br /&gt;
&lt;br /&gt;
Vorteile des Verfahrens: die Richtigkeit der Syntax lässt sich leicht vom Compiler/Interpreter überprüfen (mehr dazu in der Theoretischen Informatik und Compilerbau). Somit ist es die einfachste Möglichkeit, viele inkorrekte Programme schnell zu erkennen und zurückzuweisen.&lt;br /&gt;
  &amp;gt;&amp;gt;&amp;gt; if a==0&lt;br /&gt;
    File &amp;quot;&amp;lt;stdin&amp;gt;&amp;quot;, line 1&lt;br /&gt;
      if a==0&lt;br /&gt;
            ^&lt;br /&gt;
  SyntaxError: invalid syntax&lt;br /&gt;
&lt;br /&gt;
=== Typprüfung ===&lt;br /&gt;
Ein Typ definiert Gruppierung der Daten und die Operationen, die für diese Datengruppierung erlaubt sind(konkreter Typ) bzw. die Bedeutung der Daten und die erlaubten Operationen (abstrakter Datentyp, vgl. Dreieck aus der [[Einführung#Definition von Datenstrukturen|ersten Vorlesung]]). Typen sind Zusicherungen an den Algorithmus und den Compiler/Interpreter, dass Daten und deren Operationen bestimmte semantische Bedingungen einhalten. Wenn man innerhalb des Algorithmus mit Typen arbeitet, darf man von der semantischen Korrektheit der erlaubten Operationen ausgehen. Umgekehrt können Operationen, die zu Typkonflikten führen würden, leicht als inkorrekt zurückgeweisen werden.&lt;br /&gt;
&lt;br /&gt;
Vorteile des Verfahrens: Typprüfung ist teuerer als syntaktische Prüfung, aber billiger als andere Prüfungen der Korrektheit (mehr dazu im Kapitel [[Generizität]]).&lt;br /&gt;
  &amp;gt;&amp;gt;&amp;gt; a=3&lt;br /&gt;
  &amp;gt;&amp;gt;&amp;gt; b=None&lt;br /&gt;
  &amp;gt;&amp;gt;&amp;gt; a+b&lt;br /&gt;
  Traceback (most recent call last):&lt;br /&gt;
    File &amp;quot;&amp;lt;stdin&amp;gt;&amp;quot;, line 1, in &amp;lt;module&amp;gt;&lt;br /&gt;
  TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'&lt;br /&gt;
&lt;br /&gt;
In python ist (ebenso wie in vielen anderen Programmiersprachen) explizite Typprüfung möglich:&lt;br /&gt;
  &amp;gt;&amp;gt;&amp;gt; import types&lt;br /&gt;
  &amp;gt;&amp;gt;&amp;gt; a=3&lt;br /&gt;
  &amp;gt;&amp;gt;&amp;gt; b=None&lt;br /&gt;
  &amp;gt;&amp;gt;&amp;gt; if isinstance(b, types.IntType): # prüft, ob b ein Integer ist&lt;br /&gt;
  ...     print a+b&lt;br /&gt;
  ... else:&lt;br /&gt;
  ...     raise TypeError, &amp;quot;b ist kein Integer&amp;quot; # falls b kein Integer ist, wird ein TypeError ausgelöst&lt;br /&gt;
  ... &lt;br /&gt;
 &lt;br /&gt;
  Traceback (most recent call last):&lt;br /&gt;
    File &amp;quot;&amp;lt;stdin&amp;gt;&amp;quot;, line 4, in &amp;lt;module&amp;gt;&lt;br /&gt;
  TypeError: b ist kein Integer&lt;br /&gt;
&lt;br /&gt;
== Formaler Korrektheitsbeweis ==&lt;br /&gt;
=== (Halb-)Automatisches Beweisen ===&lt;br /&gt;
Man versucht, die Hypothese H: ''Algorithmus ist korrekt'' entweder mathematisch zu beweisen oder zu widerlegen. Dieses Beweisverfahren heißt dann halbautomatisch, wenn der Mensch in den Entscheidungsprozess miteinbezogen wird.&lt;br /&gt;
&lt;br /&gt;
Um den Beweis durchführen zu können, ist folgendes nötig:&lt;br /&gt;
;eine [http://en.wikipedia.org/wiki/Formal_specification formale Spezifikation] des Algorithmus: eine formale Spezifikation wird in einer [http://en.wikipedia.org/wiki/Specification_language Spezifikationssprache] geschrieben (z.B. [http://en.wikipedia.org/wiki/Z_notation Z]). Sie ist &lt;br /&gt;
:* deklarativ (d.h. beschreibt, was das Programm tun soll, ist selbst aber nicht ausführbar)&lt;br /&gt;
:* formal präzise (kann nur auf eine einzige Weise interpretiert werden)&lt;br /&gt;
:* hierarchisch aufgebaut (eine Spezifikation für einen komplizierten Algorithmus greift auf Spezifikationen für einfache Bestandteile dieses Algorithmus zurück)&lt;br /&gt;
:* so einfach, dass ihre Korrektheit für einen Menschen mit entsprechender Erfahrung unmittelbar einsichtig ist (denn eine Spezifikation kann nicht formal bewiesen werden - dafür wäre eine weitere Spezifikation nötig, die auch bewiesen werden müsste usw.)&lt;br /&gt;
;ein axiomatisiertes Programmiermodell: zum Beispiel&lt;br /&gt;
:* eine axiomatisierbare Programmiersprache, wie z.B. WHILE-Programm (s. [[Einführung#Zur Frage der elementaren Schritte|erste Vorlesung]]), Pascal (siehe dazu Hoare's [http://delivery.acm.org/10.1145/70000/63445/cb-p153-hoare.pdf?key1=63445&amp;amp;key2=5041959021&amp;amp;coll=ACM&amp;amp;dl=ACM&amp;amp;CFID=15151515&amp;amp;CFTOKEN=6184618 grundlegenden Artikel]) und rein funktionale Programmiersprachen&lt;br /&gt;
:* ein axiomatisierbares Subset einer Programmiersprache (die meisten Programmiersprachen sind zu komplex, um als Ganzes axiomatisierbar zu sein)&lt;br /&gt;
:* endliche Automaten&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Der Korrektheitsbeweis kann beispielsweise mit dem Hoare-Kalkül (Hoare-Logik) durchgeführt werden (Hoare erfand u.a. den Quicksort-Algorithmus). Diese Methode wurde in &lt;br /&gt;
:  C.A.R. Hoare: ''&amp;quot;An Axiomatic Basis for Computer Programming&amp;quot;'', Communications of the ACM, 1969 [http://www.cs.ucsb.edu/~kemm/courses/cs266/hoare69.pdf] &lt;br /&gt;
erstmalig beschrieben. Im folgenden wird das Verfahren an einem Beispiel erläutert.&lt;br /&gt;
&lt;br /&gt;
==== Beispiel-Algorithmus ====&lt;br /&gt;
Zuerst brauchen wir einen Algorithmus, den wir auf Korrektheit prüfen wollen. Wir nehmen als Beispiel die Division x/y durch sukzessives Subtrahieren.&lt;br /&gt;
&lt;br /&gt;
 Vorbedingungen:&lt;br /&gt;
    int x,y&lt;br /&gt;
   0 &amp;lt; y &amp;lt;= x&lt;br /&gt;
 Gesucht:&lt;br /&gt;
    Quotient q, Rest r&lt;br /&gt;
 Algorithmus:&lt;br /&gt;
    r = x&lt;br /&gt;
    q = 0&lt;br /&gt;
    while y &amp;lt;= r:&lt;br /&gt;
        r = r - y&lt;br /&gt;
        q = q + 1&lt;br /&gt;
 Nachbedingungen:&lt;br /&gt;
    x == r + y*q and r &amp;lt; y&lt;br /&gt;
&lt;br /&gt;
==== Aufbau der Hoare-Logik ====&lt;br /&gt;
&lt;br /&gt;
Grundlegende syntaktische Struktur:&lt;br /&gt;
: p {Q} r&lt;br /&gt;
mit '''p''':Vorbedingung, '''Q''': Operation, '''r''': Nachbedingung.&lt;br /&gt;
Es bedeutet also schlicht: wenn man im Zustand '''p''' ist und eine Operation '''Q''' ausführt, kommt man in den Zustand '''r'''. Hat eine Operation keine Vorbedingung, schreibt man &lt;br /&gt;
: true {Q} r&lt;br /&gt;
&lt;br /&gt;
Die Hoare-Logik besteht aus 5 Axiomen:&lt;br /&gt;
;D0 - Axiom der Zuweisung: (Rule of Assignment)&lt;br /&gt;
:: R[t] {x=t} R[x]&lt;br /&gt;
  &lt;br /&gt;
: '''Beispiel:''' t==5 {x=t} x==5&lt;br /&gt;
&lt;br /&gt;
:Vorbedingung und Nachbedingung sind gleich, mit Ausnahme der Variablen x und t, die in der Zuweisung verknüpft werden: Man erhält die Vorbedingung, wenn man in der Nachbedingung alle Vorkommen von x (bzw. allgemein: alle Vorkommen der linken Variable der Zuweisung) durch t (bzw. allgemein: durch die rechte Variable der Zuweisung) ersetzt.&lt;br /&gt;
&lt;br /&gt;
;D1 - Konsequenzregeln: (Rules of Consequence, besteht aus zwei Axiomen)&lt;br /&gt;
:'''D1(a):''' wenn gilt&lt;br /&gt;
:: P {Q} R und R &amp;amp;rArr; S&lt;br /&gt;
:dann gilt auch&lt;br /&gt;
:: P {Q} S&lt;br /&gt;
:'''D1(b):''' wenn gilt &lt;br /&gt;
:: P {Q} R und S &amp;amp;rArr; P&lt;br /&gt;
:dann gilt auch&lt;br /&gt;
:: S {Q} R&lt;br /&gt;
:'''Beispiel:''' Für jede ganze Zahl gilt (x&amp;gt;5) &amp;amp;rArr; (x&amp;gt;0). Gilt außerdem (x&amp;gt;5) dann gilt erst recht (x&amp;gt;0).&lt;br /&gt;
&lt;br /&gt;
;D2 - Sequenzregel: (Rule of Composition)&lt;br /&gt;
:wenn gilt&lt;br /&gt;
:: P {Q&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;} R&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; und R&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; {Q&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;} R &lt;br /&gt;
:dann gilt auch&lt;br /&gt;
:: P {Q&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;, Q&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;} R&lt;br /&gt;
:Das heißt: wenn man P hat und Q&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; darauf anwendet, kommt man zu R&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;. Wenn man R&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; hat und Q&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; darauf anwendet, kommt man zu R. Deshalb kann man das so verkürzen: wenn man P hat und nacheinander Q&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; und Q&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; darauf anwendet, kommt man zu R.&lt;br /&gt;
&lt;br /&gt;
;D3 - Iterationsregel: (Rule of Iteration)&lt;br /&gt;
:wenn gilt&lt;br /&gt;
:: (P &amp;amp;and; B) {S} P&lt;br /&gt;
:dann gilt auch&lt;br /&gt;
:: P { while B do S } (&amp;amp;not;B &amp;amp;and; P)&lt;br /&gt;
:P wird dabei als '''Schleifeninvariante''' bezeichnet, weil es sowohl in der Vor- als auch in der Nachbedingung gilt. B ist die '''Schleifenbedingung''' - solange B erfüllt ist, wird die Schleife weiter ausgeführt.&lt;br /&gt;
&lt;br /&gt;
Da wir in dem Divisions-Algorithmus mit dem Typ '''int''' arbeiten, brauchen wir außerdem die für diesen Typ erlaubten Operationen, also die Axiome der ganzen Zahlen.&lt;br /&gt;
: '''A1:''' Kommutativität  x+y=y+x, x*y=y*x&lt;br /&gt;
: '''A2:''' Assoziativität  (x+y)+z=x+(y+z), (x*y)*z=x*(y*z)&lt;br /&gt;
: '''A3:''' Distributivität  x*(y+z)=x*y+x*z&lt;br /&gt;
: '''A4:''' Subtraktion (Inverses Element)  y&amp;amp;le;x &amp;amp;rArr; (x-y)+y=x&lt;br /&gt;
: '''A5:''' Neutrale Elemente  x+0=x, x*0=0, x*1=x&lt;br /&gt;
&lt;br /&gt;
==== Beweisen des Algorithmus ====&lt;br /&gt;
Vorbedingung: 0 &amp;lt; y,x&lt;br /&gt;
&lt;br /&gt;
Schleifeninvariante P (gleichzeitig Nachbedingung): x == y*q + r&lt;br /&gt;
  (1)  true &amp;amp;rArr; x==x+y*0                                          y*0==0 und x==x+0 folgen aus A5&lt;br /&gt;
  (2)  x==x+y*0              {r=x}                  x==r+y*0     D0: ersetze x durch r&lt;br /&gt;
  (3)  x==r+y*0              {q=0}                  x==r+y*q     D0: ersetze 0 durch q&lt;br /&gt;
  (4)  true                  {r=x}                  x==r+y*0     D1(b): kombiniere (1) und (2)&lt;br /&gt;
  (5)  true                  {r=x, q=0}             x==r+y*q     D2: kombiniere (4) und (3)&lt;br /&gt;
  (6)  x==r+y*q &amp;amp;and; y=r &amp;amp;rArr; x==(r-y)+y*(1+q)                       folgt aus A1...A5&lt;br /&gt;
  (7)  x==(r-y)+y*(1+q)      {r=r-y}                x==r+y*(1+q) D0: ersetze (r-y) durch r&lt;br /&gt;
  (8)  x==r+y*(1+q)          {q=q+1}                x==r+y*q     D0: ersetze (q+1) durch q&lt;br /&gt;
  (9)  x==(r-y)+y*(1+q)      {r=r-y, q=q+1}         x==r+y*q     D2: kombiniere (7) und (8)&lt;br /&gt;
  (10) x==r+y*q &amp;amp;and; y&amp;amp;le;r        {r=r-y, q=q+1}         x==r+y*q     D1(b): kombiniere (6) und (9)&lt;br /&gt;
  (11) x==r+y*q    {while y&amp;amp;le;r do (r=r-y, q=q+1)} x==r+y*q &amp;amp;and; &amp;amp;not;(y&amp;amp;le;r) D3: transformiere (10)&lt;br /&gt;
  (12) true        {r=x, q=0, &lt;br /&gt;
                    while y&amp;amp;le;r do (r=r-y, q=q+1)} x==r+y*q &amp;amp;and; &amp;amp;not;(y&amp;amp;le;r) D2: kombiniere (5) und (11)&lt;br /&gt;
&lt;br /&gt;
Im obigen Beweis ergibt sich sogar ''true'' als Vorbedingung (i.e. es gibt keine Vorbedingung). Dies liegt daran, dass Hoare in seinem Artikel durchweg von nicht-negativen Zahlen ausgeht. Diese Annahme wird beim Beweis von Zeile (6) benutzt.&lt;br /&gt;
&lt;br /&gt;
In der Praxis führt man solche Beweise natürlich nicht von Hand, sondern benutzt geeignete Programme, sogenannte [http://en.wikipedia.org/wiki/Automated_theorem_proving automatische Beweiser], die man allerding oft interaktiv steuern muss, weil der Beweis ohne diese Hilfe zu lange dauern würde.&lt;br /&gt;
&lt;br /&gt;
=== (Halb-)Automatisches Verfeinern ===&lt;br /&gt;
Dieses Verfahren ist beliebter, als das (halb-)automatische Beweisen. Die formale Spezifikation wird nach bestimmten, semantik-erhaltenden Transformationsregeln in ein ausführbares Programm umgewandelt. Mehr dazu z.B. in der [http://en.wikipedia.org/wiki/Program_refinement Wikipedia (Program refinement)]. Der Vorteil dieser Methode besteht darin, dass man die Transformationsregeln so definieren kann, dass nur das axiomatisierte Subset der Zielsprache benutzt wird. Dadurch wird der Korrektheitsbeweis stark vereinfacht.&lt;br /&gt;
&lt;br /&gt;
==Software-Tests==&lt;br /&gt;
&lt;br /&gt;
Dijkstra [http://de.wikipedia.org/wiki/Edsger_Wybe_Dijkstra] ließ einmal den Satz verlauten: &amp;quot;Tests können nie die Abwesenheit von Fehlern beweisen [Anwesenheit schon]&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Nach solch einer Aussage stellt sich die Frage, ob es sich überhaupt lohnt, mit dem Testverfahren die Korrektheit eines Algorithmus zu zeigen. Es erscheint einem doch plausibler sich auf die &amp;quot;formalen Methoden&amp;quot; zu berufen, mit dem Wissen, dass diese uns tatsächlich einen Beweis liefern können, ob nun H oder nicht H gilt. Zudem kommt noch erschwerend hinzu, dass es bei Tests bisher keine Theorie gibt, die sicherstellt, dass das Testprogramm einen vorhandenen Fehler zumindest mit hoher Wahrscheinlichkeit findet.&lt;br /&gt;
&lt;br /&gt;
Ein [http://de.wikipedia.org/wiki/Softwaretest Software-Test] versucht, ein Gegenbeispiel zur Hypothese H &amp;quot;der Algorithmus ist korrekt&amp;quot; zu finden. Dabei gibt es 4 Möglichkeiten:&lt;br /&gt;
 &lt;br /&gt;
   Algorithmus	   Testantwort	&lt;br /&gt;
      +	               +	        Algorithmus ist richtig, kein Gegenbeispiel gefunden&lt;br /&gt;
      -	               -	        Alg. ist falsch, und der Test erkennt den Fehler&lt;br /&gt;
      +	               -	        Bug im Test (Gegenbeispiel, obwohl Alg. richtig ist)&lt;br /&gt;
      -	               +	        Test hat versagt, da er den Fehler im Alg. nicht erkannt hat&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Wenn ein Gegenbeispiel zu H gefunden wird, kann man den Algorithmus (oder den Test) debuggen. Wird hingegen keines gefunden, nimmt man an, dass der Algorithmus korrekt ist. Man sieht, dass diese Annahme im Fall 4 nicht stimmt. Da Softwaretests jedoch in der Praxis sehr erfolgreich verwendet werden, ist dieser Fall offenbar nicht so häufig, dass man das Testen als Methode generell ablehnen müßte.&lt;br /&gt;
&lt;br /&gt;
=== Beispiel für das Testen: Freivalds Algorithmus ===&lt;br /&gt;
&lt;br /&gt;
Wir wollen die Wahrscheinlichkeit, dass ein Test einen vorhandenen Fehler übersieht, am Beispiel des [http://en.wikipedia.org/wiki/Freivald's_algorithm Algorithmus von Freivald] studieren. Es handelt sich dabei um einen randomisierten Algorithmus zum Testen der Matrixmultiplikation (siehe J. Hromkovič: ''&amp;quot;Randomisierte Algorithmen&amp;quot;'', Teubner 2004). Ziel dieses Algorithmuses ist es, die Hypothese H: &amp;quot;C ist das Produkt der Matrizen A und B&amp;quot; durch ein Gegenbeispiel zu widerlegen, wobei der Test einen anderen Algorithmus verwendet, um Vergleichsdaten zu gewinnen.&lt;br /&gt;
&lt;br /&gt;
  gegeben:&lt;br /&gt;
       Matrizen A, B, C  der Größe NxN &lt;br /&gt;
       Testhypothese H:  &amp;lt;tt&amp;gt;A*B == C&amp;lt;/tt&amp;gt;  Matrixmultiplikation (d.h. C wurde vorher durch C = mmul(A, B) berechnet, &lt;br /&gt;
                                            wobei mmul() der zu testende Multiplikationsalgorithmus ist).&lt;br /&gt;
 &lt;br /&gt;
  (1) Initialisierung      &lt;br /&gt;
       wähle Zufallsvektor der Länge N aus Nullen und Einsen: &amp;lt;math&amp;gt;\alpha \in \{0, 1\}^N &amp;lt;/math&amp;gt;  &lt;br /&gt;
  (2) Matrix-Vektor-Multiplikation (keine Matrix-Matrix-Multiplikation, denn die soll ja gerade verifiziert werden)&lt;br /&gt;
 &lt;br /&gt;
       &amp;lt;math&amp;gt;\left.\begin{array}{l}&lt;br /&gt;
                \beta = B*\alpha \\&lt;br /&gt;
                \gamma=A*\beta&lt;br /&gt;
                \end{array}\right\}A*(B*\alpha) == (A*B)*\alpha&lt;br /&gt;
       &amp;lt;/math&amp;gt; &lt;br /&gt;
 &lt;br /&gt;
       &amp;lt;math&amp;gt;\delta=C*\alpha&amp;lt;/math&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
  (3) Test der Korrektheit: falls &amp;lt;tt&amp;gt;A*B == C&amp;lt;/tt&amp;gt;, liefert der folgende Test stets &amp;lt;tt&amp;gt;true&amp;lt;/tt&amp;gt;:&lt;br /&gt;
 &lt;br /&gt;
       return   γ==δ&lt;br /&gt;
&lt;br /&gt;
Wir analysieren nun, mit welcher Wahrscheinlichkeit der Algorithmus den Fehler findet, wenn es denn einen gibt, d.h.&lt;br /&gt;
   &lt;br /&gt;
*Wahrscheinlichkeit '''p''', dass Freivalds Algorithmus den Fehler findet&amp;lt;br/&amp;gt;&lt;br /&gt;
oder&amp;lt;br/&amp;gt;&lt;br /&gt;
*Wahrscheinlichkeit '''q = 1 - p''', dass Freivalds Algorithmus den Fehler '''nicht''' findet.&lt;br /&gt;
&lt;br /&gt;
Wir schätzen diese Wahrscheinlichkeit ab für den einfachen Fall N=2. Wir definieren:&lt;br /&gt;
    &lt;br /&gt;
   &amp;lt;math&amp;gt;C=&lt;br /&gt;
  \begin{pmatrix} &lt;br /&gt;
    c_{11} &amp;amp; c_{12}  \\ &lt;br /&gt;
    c_{21} &amp;amp; c_{22}  &lt;br /&gt;
  \end{pmatrix},\qquad&lt;br /&gt;
\alpha=\begin{pmatrix}&lt;br /&gt;
    \alpha_1 \\&lt;br /&gt;
    \alpha_2 &lt;br /&gt;
     \end{pmatrix},\qquad&lt;br /&gt;
 \delta=\begin{pmatrix}&lt;br /&gt;
    \delta_1 \\&lt;br /&gt;
    \delta_2&lt;br /&gt;
 \end{pmatrix}&lt;br /&gt;
  = \begin{pmatrix}&lt;br /&gt;
    c_{11}\alpha_1 + c_{12}\alpha_2 \\&lt;br /&gt;
    c_{21}\alpha_1 + c_{22}\alpha_2&lt;br /&gt;
   \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Fallunterscheidung:'''&lt;br /&gt;
      &lt;br /&gt;
'''Fall 1:'''  C enthält genau 1 Fehler, z.B. &amp;lt;math&amp;gt;c_{11}&amp;lt;/math&amp;gt; hat falschen Wert&lt;br /&gt;
&lt;br /&gt;
:Der Fehler wird gefunden, wenn &amp;lt;math&amp;gt;\delta_1 \ne \gamma_1 \Leftrightarrow\alpha_1\ne 0&amp;lt;/math&amp;gt;. Da &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt; eine Zufallszahl aus &amp;lt;math&amp;gt;\{0,1\}&amp;lt;/math&amp;gt; ist, folgt daraus, dass '''p''' = '''q''' = &amp;lt;math&amp;gt;\frac{1}{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
       &lt;br /&gt;
'''Fall 2:'''  C enthält 2 Fehler&lt;br /&gt;
:(a)   in verschiedenen Zeilen und Spalten, z.B. &amp;lt;math&amp;gt;c_{11}&amp;lt;/math&amp;gt; und &amp;lt;math&amp;gt;c_{22}&amp;lt;/math&amp;gt;. Es gilt: Der Fehler in &amp;lt;math&amp;gt;c_{11}&amp;lt;/math&amp;gt; wird gefunden, wenn &amp;lt;math&amp;gt;\delta_1 \ne \gamma_1 \Leftrightarrow \alpha_1\ne 0&amp;lt;/math&amp;gt;. Unabhängig davon wird der Fehler in &amp;lt;math&amp;gt;c_{22}&amp;lt;/math&amp;gt; gefunden, wenn &amp;lt;math&amp;gt;\delta_2 \ne \gamma_2 \Leftrightarrow \alpha_2\ne 0&amp;lt;/math&amp;gt;. Da &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt; und &amp;lt;math&amp;gt;\alpha_2&amp;lt;/math&amp;gt; statistisch unabhängig sind, ist die Wahrscheinlichkeit für jedes dieser Ereignisse &amp;lt;math&amp;gt;q_1&amp;lt;/math&amp;gt; bzw. &amp;lt;math&amp;gt;q_2&amp;lt;/math&amp;gt; jeweils &amp;lt;math&amp;gt;\frac{1}{2}&amp;lt;/math&amp;gt;, und die Gesamtwahrscheinlichkeit '''q''', dass ''keiner'' der beiden Fehler gefunden wird, ist deren Produkt: '''q''' = &amp;lt;math&amp;gt;q_1*q_2 = \frac{1}{2}* \frac{1}{2} = \frac{1}{4}&amp;lt;/math&amp;gt;.        &lt;br /&gt;
&lt;br /&gt;
:(b) in verschiedenen Zeilen, gleichen Spalten, z.B. &amp;lt;math&amp;gt;c_{11}&amp;lt;/math&amp;gt; und &amp;lt;math&amp;gt;c_{21}&amp;lt;/math&amp;gt;. Es gilt: Der Fehler in &amp;lt;math&amp;gt;c_{11}&amp;lt;/math&amp;gt; wird gefunden, wenn &amp;lt;math&amp;gt;\delta_1 \ne \gamma_1 \Leftrightarrow \alpha_1\ne 0&amp;lt;/math&amp;gt;. Das gleiche gilt für den Fehler in &amp;lt;math&amp;gt;c_{21}&amp;lt;/math&amp;gt;. Die Wahrscheinlichkeit '''q''', dass ''keiner'' der beiden Fehler gefunden wird, ist demzufolge: '''q''' = &amp;lt;math&amp;gt;\frac{1}{2}&amp;lt;/math&amp;gt;.&lt;br /&gt;
               &lt;br /&gt;
:(c) in der gleichen Zeile, z.B. &amp;lt;math&amp;gt;c_{11}&amp;lt;/math&amp;gt; und &amp;lt;math&amp;gt;c_{12}&amp;lt;/math&amp;gt;. Es gilt: Der Fehler wird gefunden, wenn &amp;lt;math&amp;gt;\delta_1 \ne \gamma_1 \Leftrightarrow \alpha_1*c_{11}+\alpha_2*c_{12}\ne 0&amp;lt;/math&amp;gt;. Hier treten nun zwei ungünstige Fälle auf: &lt;br /&gt;
::1) Der Fehler wird u.a. dann nicht gefunden, wenn &amp;lt;math&amp;gt;\alpha_1 = \alpha_2=0&amp;lt;/math&amp;gt;. Die Wahrscheinlichkeit dafür ist  wieder '''q'''=&amp;lt;math&amp;gt;\frac{1}{4}&amp;lt;/math&amp;gt;&lt;br /&gt;
::2) &amp;lt;math&amp;gt;\alpha_1=\alpha_2=1&amp;lt;/math&amp;gt; (dies geschieht ebenfalls mit Wahrscheinlichkeit &amp;lt;math&amp;gt;\frac{1}{4}&amp;lt;/math&amp;gt;), aber die Werte &amp;lt;math&amp;gt;c_{11}&amp;lt;/math&amp;gt; und &amp;lt;math&amp;gt;c_{12}&amp;lt;/math&amp;gt; sind &amp;quot;zufälligerweise&amp;quot; so falsch, dass sich die Fehler gegenseitig aufheben. Die Wahrscheinlichkeit, dass beide Bedingungen gelten, ist auf jeden Fall '''q''' =  &amp;lt;math&amp;gt;\epsilon&amp;lt;\frac{1}{4}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Analog behandelt man die Fälle, dass C drei oder vier Fehler enthält. Fasst man die Fälle zusammen, ergibt sich, dass die Wahrscheinlichkeit, einen vorhandenen Fehler '''nicht''' zu entdecken, sicher kleiner als &amp;lt;math&amp;gt;\frac{1}{2}&amp;lt;/math&amp;gt; ist. Dies gilt auch allgemein:&lt;br /&gt;
&lt;br /&gt;
;Satz:&lt;br /&gt;
*Die Wahrscheinlichkeit, dass Freivalds Algorithmus einen vorhandenen Fehler '''nicht''' findet, ist '''q''' &amp;lt; &amp;lt;math&amp;gt;\frac{1}{2}&amp;lt;/math&amp;gt;. Wir haben diesen Satz oben für N=2 bewiesen, ein vollständiger Beweis findet sich in der [http://en.wikipedia.org/wiki/Freivald's_algorithm#Error_Analysis Wikipedia].&lt;br /&gt;
 &lt;br /&gt;
;Folgerung: &lt;br /&gt;
*Lässt man Freivalds Algorithmus mit verschiedenen &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt; k-mal laufen, gilt &amp;lt;math&amp;gt;q_k &amp;lt; 2^{-k}&amp;lt;/math&amp;gt; für die Wahrscheinlichkeit, dass '''keiner''' der k Durchläufe einen vorhandenen Fehler findet. Diese Wahrscheinlichkeit konvergiert sehr schnell gegen 0. Das heißt, der Algorithmus findet mit beliebig hoher Wahrscheinlichkeit ein Gegenbeispiel zu H (falls es eins gibt), wenn man ihn nur genügend oft mit jeweils anderen Zufallszahlen wiederholt. Daraus folgt, dass Testen ein effektives Fehlersuchverfahren sein kann -- die oben erwähnte Einschränkung von Dijktra trifft zwar zu, aber Tests, die mit so hoher Wahrscheinlichkeit funktionieren, sind für die Praxis meistens vollkommen ausreichend.&lt;br /&gt;
&lt;br /&gt;
=== Vergleich formaler Korrektheitsbeweis und Testen ===&lt;br /&gt;
&lt;br /&gt;
Nachdem nun die formalen Methoden sowie der Software-Test vorgestellt worden sind, ist nun die Frage aufzugreifen, welcher der beiden Vorgänge der bessere ist. Allgemein gilt:&lt;br /&gt;
&lt;br /&gt;
;randomisierte Algorithmen&lt;br /&gt;
               &lt;br /&gt;
*sind schnell und einfach:&lt;br /&gt;
#da die Operationen einfach sind und wenig Zeit kosten&lt;br /&gt;
#des öfteren eine Auswahl vorgenommen wird ohne die Gesamtmenge näher zu betrachten&lt;br /&gt;
#die Auswahl selbst aufgrund einfacher Kriterien (bspw. zufällige Auswahl) erfolgt&lt;br /&gt;
*können Lösungen approximieren und liefern gute approximative Lösungen&lt;br /&gt;
&lt;br /&gt;
;formaler Korrektheitsbeweis mit deterministischen Algorithmen (siehe auch [http://de.wikipedia.org/wiki/Determinismus_(Algorithmus)])&lt;br /&gt;
  &lt;br /&gt;
*bei jedem Aufruf des Beweisers werden immer die selben Schritte durchlaufen&lt;br /&gt;
*keine Zufallswerte&lt;br /&gt;
*komplexer Aufbau&lt;br /&gt;
*oft sehr lange Laufzeit, z.B. mehrere Tage oder gar Monate&lt;br /&gt;
&lt;br /&gt;
Für die formalen Methoden spricht, dass man mit ihnen im Prinzip beweisen kann, dass H nun entweder tatsächlich falsch oder richtig ist. Die formalen Beweise bei realen Problemen sind allerdings so kompliziert, dass sie ebenfalls mit Computerhilfe erbracht werden müssen. Dadurch liegt auch hier keine 100%-ige Korrektheitsgarantie vor: Auch formale Methoden können zum falschen Ergebnis kommen, z.B. durch Hardwarefehler, Compilerbugs, oder unvorhergesehenes Umkippen von Bits (z.B. durch kosmische Strahlung -- diese Gefahr ist im Weltall sehr ernst zu nehmen). Die Möglichkeit von Hardwarefehlern wirkt sich auf die formalen Methoden wesentlich stärker aus, weil diese typischerweise wesentlich längere Laufzeiten haben als entsprechende Testalgorithmen. Es kann deshalb durchaus vorkommen, dass Tests eine höhere Erfolgswahrscheinlichkeit haben als ein formaler Beweis, wie die folgende Beispielrechnung zeigt. Wir nehmen an, dass die Hardware eine &amp;quot;Halbwertszeit&amp;quot; von 50 Millionen Sekunden hat, d.h. ein Hardwarefehler tritt im Durchschnitt etwa alle 20 Monate auf. Dann ist die Wahrscheinlichkeit, dass ein deterministischer Algorithmus '''nicht''' zum Ergebnis (oder zum falschen Ergebnis) kommt:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;math&amp;gt;q_{\mathrm{Beweis}} \approx 0.001&amp;lt;/math&amp;gt;, falls der Beweisalgorithmus 1 Tag benötigt,&lt;br /&gt;
* &amp;lt;math&amp;gt;q_{\mathrm{Beweis}} \approx 0.01&amp;lt;/math&amp;gt;, falls der Beweisalgorithmus 1 Woche benötigt,&lt;br /&gt;
* &amp;lt;math&amp;gt;q_{\mathrm{Beweis}} \approx 0.035&amp;lt;/math&amp;gt;, falls der Beweisalgorithmus 1 Monat benötigt.&lt;br /&gt;
&lt;br /&gt;
Zum Vergleich nehmen wir an, dass der entsprechende Softwaretest einmal pro Sekunde ausgeführt werden kann, und dass jeder Durchlauf den Fehler mit einer Wahrscheinlichkeit von &amp;lt;math&amp;gt;\frac{1}{2}&amp;lt;/math&amp;gt; '''nicht''' findet. Unter gleichzeitiger Berücksichtigung der Wahrscheinlichkeit von Hardwarefehlern gilt dann&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;math&amp;gt;q_{\mathrm{Test}} \approx 0.5&amp;lt;/math&amp;gt;, falls der Test 1-mal wiederholt wird,&lt;br /&gt;
* &amp;lt;math&amp;gt;q_{\mathrm{Test}} \approx 0.001&amp;lt;/math&amp;gt;, falls der Test 10-mal wiederholt wird,&lt;br /&gt;
* &amp;lt;math&amp;gt;q_{\mathrm{Test}} \approx 10^{-6}&amp;lt;/math&amp;gt;, falls der Test 100-mal wiederholt wird.&lt;br /&gt;
&lt;br /&gt;
Mit anderen Worten: hier ist das Testen vorzuziehen, weil es unter realistischen Bedingungen eine höhere Erfolgswahrscheinlichkeit hat als der formale Beweis. Leider gibt es bisher keine Theorie, mit deren Hilfe man für ein gegebenes Problem systematisch Tests konstruieren kann, deren Misserfolgswahrscheinlichkeit bei wiederholter Anwendung garantiert so schnell gegen Null konvergiert wie die des Freivalds Algorithmus. Dies ist ein offenes Problem der Informatik.&lt;br /&gt;
&lt;br /&gt;
==Anwendung des Softwaretestverfahren==&lt;br /&gt;
===Beispiel an Python-Code===&lt;br /&gt;
&lt;br /&gt;
Man betrachte die Aufgabe, aus einer Zahl x die Wurzel zu ziehen. Dies kann man erreichen, indem man mit Hilfe des Newtonschen Iterationsverfahrens eine Nullstelle des Polynoms &lt;br /&gt;
:&amp;lt;math&amp;gt;f(y) = x - y^2 = 0&amp;lt;/math&amp;gt; &lt;br /&gt;
sucht. Ist eine Näherungslösung &amp;lt;math&amp;gt;y^{(t)}&amp;lt;/math&amp;gt; bekannt, erhält man eine bessere Näherung durch&lt;br /&gt;
:&amp;lt;math&amp;gt;y^{(t+1)} = y^{(t)} - \frac{f(y^{(t)})}{f'(y^{(t)})}&amp;lt;/math&amp;gt;.&lt;br /&gt;
Mit &amp;lt;math&amp;gt;f\,'(y) = -2y&amp;lt;/math&amp;gt; wird das zu&lt;br /&gt;
:&amp;lt;math&amp;gt;y^{(t+1)} = y^{(t)} + \frac{x-(y^{(t)})^2}{2y^{(t)}}=\frac{y^{(t)}+x/y^{(t)}}{2}&amp;lt;/math&amp;gt;. &lt;br /&gt;
Im Spezialfall des Wurzelziehens war diese Newton-Iteration übrigens bereits im Altertum als [http://en.wikipedia.org/wiki/Babylonian_method#Babylonian_method Babylonische Methode] bekannt. Man kann dieselbe durch das folgende (allerding noch nicht korrekte) Pythonprogramm realisieren:&lt;br /&gt;
&lt;br /&gt;
           1   def sqrt(x):&lt;br /&gt;
           2       if (x&amp;lt;0):&lt;br /&gt;
           3           raise ValueError(&amp;quot;sqrt of negative number&amp;quot;)&lt;br /&gt;
           4       y = x / 2&lt;br /&gt;
           5       while y*y != x:&lt;br /&gt;
           6           y =(y + x/y) / 2&lt;br /&gt;
           7       return y:&lt;br /&gt;
&lt;br /&gt;
Für den oben aufgeführten Pythoncode können Tests mit Hilfe des Python-Moduls &amp;quot;[http://docs.python.org/lib/module-unittest.html unittest]&amp;quot; geschrieben werden (siehe auch Übungsaufgaben). Wir erklären hier die wichtigsten Befehle aus diesem Modul. Wir implementieren eine Testfunktionen (diese muss, wie im Python-Handbuch beschrieben, Methode einer Testklasse sein).&lt;br /&gt;
&lt;br /&gt;
   class SqrtTest(unittest.TestCase):&lt;br /&gt;
     def testsqrt(self): &lt;br /&gt;
         ...&lt;br /&gt;
&lt;br /&gt;
Zunächst muss man prüfen, ob die Vorbedingung korrekt getestet wird, d.h. ob bei einer negativen Zahl x eine Exception ausgelöst wird; dafür benötigt man &lt;br /&gt;
&lt;br /&gt;
         self.assertRaises(ValueError, sqrt, -1) &lt;br /&gt;
Sollte keine Exception vom Type &amp;lt;tt&amp;gt;ValueError&amp;lt;/tt&amp;gt; ausgelöst werden, dann würde der Test hier einen Fehler signalisieren. Dieser Test funktioniert aber.&lt;br /&gt;
&lt;br /&gt;
Weiter testen wir einige Beispiele, deren Wurzel wir kennen:&lt;br /&gt;
&lt;br /&gt;
         self.assertEqual(sqrt(9),3) &lt;br /&gt;
Wäre hier das Ergebnis ungleich 3, würde ebenfalls ein Fehler signalisiert, aber es funktioniert in unserem Falle. Der Test&lt;br /&gt;
&lt;br /&gt;
         self.assertEqual(sqrt(1),1)&lt;br /&gt;
schlägt jedoch mit &amp;lt;tt&amp;gt;ZeroDivisionError&amp;lt;/tt&amp;gt; fehl! Wir sehen, dass in Zeile 4 eine Ganzzahldivision durchgeführt wird, deren Ergebnis stets abgerundet wird, was hier zu &amp;lt;tt&amp;gt;y = 0&amp;lt;/tt&amp;gt; und damit zum Fehler in Zeile 6 führt. Wieso hat dann aber der erste Test &amp;lt;tt&amp;gt;sqrt(9) == 3&amp;lt;/tt&amp;gt; funktioniert? Hier gilt &amp;lt;tt&amp;gt;x / 2 == 4&amp;lt;/tt&amp;gt; und &amp;lt;tt&amp;gt;x / y == 2&amp;lt;/tt&amp;gt; (jeweils nach Abrunden), und der Mittelwert der beiden Schätzungen ist gerade &amp;lt;tt&amp;gt;y == 3&amp;lt;/tt&amp;gt;, also zufällig das richtige Ergebnis. Allgemein sehen wir jedoch, dass es nicht korrekt ist, mit ganzen Zahlen zu rechnen. Wir müssen also den Input zunächst in einen Gleitkommawert umwandeln:&lt;br /&gt;
&lt;br /&gt;
           1   def sqrt(x):&lt;br /&gt;
           1a      x = float(x)&lt;br /&gt;
           2       if (x&amp;lt;0):&lt;br /&gt;
           3           raise ValueError(&amp;quot;sqrt of negative number&amp;quot;)&lt;br /&gt;
           4       y = x / 2&lt;br /&gt;
           5       while y*y != x:&lt;br /&gt;
           6           y =(y + x/y) / 2&lt;br /&gt;
           7       return y:&lt;br /&gt;
&lt;br /&gt;
Jetzt funktionieren die vorhandenen Tests, aber bei anderen Zahlen (z.B. &amp;lt;tt&amp;gt;x = 1.21&amp;lt;/tt&amp;gt;) läuft das Programm in eine Endlosschleife. Dies liegt daran, dass durch die beschränkte Genauigkeit der Gleitkomma-Darstellung selten exakte Gleichheit in der &amp;lt;tt&amp;gt;while&amp;lt;/tt&amp;gt;-Bedingung erreicht wird. Man darf nicht auf Gleichheit prüfen, sondern muss den relativen Fehler beschränken:&lt;br /&gt;
&lt;br /&gt;
           1   def sqrt(x):&lt;br /&gt;
           1a      x = float(x)&lt;br /&gt;
           2       if (x&amp;lt;0):&lt;br /&gt;
           3           raise ValueError(&amp;quot;sqrt of negative number&amp;quot;)&lt;br /&gt;
           4       y = x / 2&lt;br /&gt;
           5       while abs(1.0 - x / y**2) &amp;gt; 1e-15:  # check for relative difference&lt;br /&gt;
           6           y =(y + x/y) / 2&lt;br /&gt;
           7       return y:&lt;br /&gt;
&lt;br /&gt;
Jetzt terminiert das Programm, aber der Test&lt;br /&gt;
&lt;br /&gt;
        self.assertEqual(sqrt(1.21)**2, 1.21)  # schlägt fehl&lt;br /&gt;
&lt;br /&gt;
schlägt wegen der beschränkten Genauigkeit der Gleitkommadarstellung fehl. Man umgeht dieses Problem, indem man im Tests selbst nur nähreungsweise Gleichheit fordert, z.B. auf 15 Dezimalstellen genau (bei 16 Dezimalen würde es nicht mehr funktionieren):&lt;br /&gt;
&lt;br /&gt;
        self.assertAlmostEqual(sqrt(1.21)**2, 1.21, 15)&lt;br /&gt;
&lt;br /&gt;
Wenden wir jetzt das ''Prinzip der Condition Coverage'' an (siehe unten), sehen wir, dass die &amp;lt;tt&amp;gt;while&amp;lt;/tt&amp;gt;-Bedingung bei allen bisherigen Tests zunächst mindestens einmal &amp;lt;tt&amp;gt;true&amp;lt;/tt&amp;gt; gewesen ist. Ein weiterer sinnvoller Tests ist deshalb einer, der diese Bedingung sofort &amp;lt;tt&amp;gt;false&amp;lt;/tt&amp;gt; macht. Dies trifft z.B. bei &amp;lt;tt&amp;gt;x == 4&amp;lt;/tt&amp;gt; zu, weil &amp;lt;tt&amp;gt;y = x / 2&amp;lt;/tt&amp;gt; hier gerade die korrekte Wurzel liefert. Wir fügen deshalb den Test&lt;br /&gt;
&lt;br /&gt;
         self.assertEqual(sqrt(4), 2) &lt;br /&gt;
&lt;br /&gt;
hinzu, der erfolgreich verläuft. Das ''Prinzip der Domänen-Zerlegung'' (siehe unten) führt uns weiter dazu, die Wurzel aus Null als sinnvollen Test zu betrachten, weil die Null am Rand des erlaubten Wertebereichs liegt. Der Test&lt;br /&gt;
&lt;br /&gt;
        self.assertEqual(sqrt(0), 0)  # schlägt fehl&lt;br /&gt;
&lt;br /&gt;
schlägt in der Tat mit einem &amp;lt;tt&amp;gt;ZeroDivisionError&amp;lt;/tt&amp;gt; fehl: In der Abfrage der &amp;lt;tt&amp;gt;while&amp;lt;/tt&amp;gt;-Bedingung wird jetzt durch &amp;lt;tt&amp;gt;y == 0&amp;lt;/tt&amp;gt; geteilt. Wir können diesen Fehler beheben, indem wir die Division aus der Bedingung eliminieren:&lt;br /&gt;
&lt;br /&gt;
           1   def sqrt(x):&lt;br /&gt;
           1a      x = float(x)&lt;br /&gt;
           2       if (x&amp;lt;0):&lt;br /&gt;
           3           raise ValueError(&amp;quot;sqrt of negative number&amp;quot;)&lt;br /&gt;
           4       y = x / 2&lt;br /&gt;
           5       while abs(y**2 - x) &amp;gt; 1e-15*x:  # check for relative difference without division&lt;br /&gt;
           6           y =(y + x/y) / 2&lt;br /&gt;
           7       return y:&lt;br /&gt;
&lt;br /&gt;
Damit ist auch dieses Problem behoben. Wir sehen also, wie das systematische Testen uns dabei hilft, Fehler im Programm zu finden und zu eliminieren.&lt;br /&gt;
&lt;br /&gt;
===Definition guter Tests===&lt;br /&gt;
&lt;br /&gt;
Wir haben gezeigt, dass Testen eine effektive Methode ist, um Fehler in Algorithmen zu finden. Allerdings gilt das nur, wenn Tests und Testdaten geschickt gewählt werden. Wir zeigen bewährte Methoden dafür. &lt;br /&gt;
&lt;br /&gt;
====Generieren von Referenzdaten====&lt;br /&gt;
&lt;br /&gt;
Wie immer man die Tests definiert hat, muss man am Ende die Ausgabe des Algorithmus mit dem korrekten Ergebnis vergleichen. Man bezeichnet ein bekanntes korrektes Ergebnis als ''Referenz-Ergebnis''. Dieses muss man aber erst einmal kennen, was sich mitunter als schwierig erweist. Folgende Verfahren haben sich als zweckmäßig erwiesen:&lt;br /&gt;
* Bei bestimmten Eingaben ist das Ergebnis für den Menschen einfach zu bestimmen, für den Algorithmus ist diese Eingabe aber ebenso schwierig wie jede andere. Dies gilt zum Beispiel für die Quadratzahlen im obigen Beispiel: der Algorithmus kennt keine Quadratzahlen und behandelt sie wie jede andere reelle Zahl. Deshalb eignen sich die Quadratzahlen zum Testen. Auch beim Sortieren kleiner Listen kann die korrekte Sortierung leicht bestimmt und als Referenz-Ergebnis abgespeichert werden. Der Test vergleicht dann einfach die Ausgabe des Sortieralgorithmus mit dem Referenz-Ergebnis.&lt;br /&gt;
* Oft kann man das korrekte Ergenis mit einem alternativen Verfahren berechnen. Dies gilt insbesondere, wenn man einen effizienten, aber komplizierten Algorithmus testen will. Dann berechnet man die Referenz-Ergebnisse mit einem langsamen, aber einfachen Verfahren. Dies ist möglich, weil man die Referenz-Ergebnisse ja abspeichern kann und der langsame Algorithmus daher nur wenige Male benutzt werden muss. Beispielsweise kann man einen komplizierten Sortieralgorithmus (Quicksort) mit Hilfe von selection sort testen.&lt;br /&gt;
* In vielen Fällen steht ein alternatives Programm zur Verfügung, z.B. eine ältere Version des zu testenden Programms, oder ein kommerzielles Programm (bzw. eine Demoversion), das dasselbe Problem löst, aber im aktuellen Kontext nicht verwendet werden kann (weil es z.B. zu teuer ist, oder nur auf einem Mac läuft). Diese Methode bietet sich auch an, wenn man einen Algorithmus aus einer Programmiersprache in eine andere portieren muss. &lt;br /&gt;
* Manchmal kann das korrekte Ergebnis nicht direkt angegeben werden, aber man kennt bestimmte Eigenschaften. Beim Sortieren kann man z.B. testen, dass kein Element des sortierten Arrays größer ist als das darauffolgende. Man testes also die Nachbedingungen. Eine abgeschwächte Versionen dieser Methode wird für randomisierte Algorithmen verwendet: Ist die Wahrscheinlichkeitsverteilung der Testeingaben bekannt, kann man die Wahrscheinlichkeitsverteilung der Ergebnisse, oder zumindest wichtige Eigenschaften wie z.B. den Mittelwert, mathematisch vorhersagen. Der Test ermittelt dann, ob die Ausgaben über viele Durchläufe des Algorithmus diese statistischen Eigenschaften aufweisen.&lt;br /&gt;
&lt;br /&gt;
====Arten von Tests====&lt;br /&gt;
&lt;br /&gt;
Man unterscheidet 3 grundlegende Arten von Tests:&lt;br /&gt;
&lt;br /&gt;
;Black-box Tests [http://en.wikipedia.org/wiki/Black_box_testing]: Hier ist dem Tester nur die Spezifikation, aber nicht die Implementation des Algorithmus bekannt. Alle Tests sowie die Eingaben und Referenz-Ergebnisse müssen aus der Spezifikation abgeleitet werden. Die automatisierte Generierung guter Tests aus der Spezifikation ist ein aktives Forschungsgebiet.&lt;br /&gt;
;Gray-box Tests (auch Glass-box Tests) [http://www.cse.fau.edu/~maria/COURSES/CEN4010-SE/C13/glass.htm]: Hier kennt der Tester auch die Implementation und kann dadurch Tests entwerfen, die für diese spezielle Implementation besonders aussagekräftig sind. Es besteht allerdings die Gefahr, dass der Tester nicht mehr unvoreingenommen an das Testproblem herangeht, und Zustände, die seiner Meinung nach gar nicht vorkommen können, auch nicht testet (erst später stellt sich heraus, dass diese Zustände doch vorkommen).&lt;br /&gt;
;White-box Tests [http://en.wikipedia.org/wiki/White_box_testing]: Hier kann der Tester die Implementation sogar in geeigneter Weise verändern, z.B. &lt;br /&gt;
:* explizite Tests für Vor- und Nachbedingungen (&amp;quot;Assertions&amp;quot;) einbauen. Dies bietet sich insbesondere in der alpha- und beta-Testphase eines Programms an, um Fehler schnell zu lokalisieren. Auch die unter Windows bekannte Dialogbox &amp;quot;Diesen Fehler bitte auch an Microsoft melden&amp;quot; wird durch solche eingebauten Assertions ausgelöst, wenn das Programm in einen illegalen Zustand geraten ist und abgebrochen werden muss.&lt;br /&gt;
:* zusätzlichen Code einbauen, der feststellt, ob alle Teile des Programms auch tatsächlich getestet wurden (&amp;quot;[http://blogs.msdn.com/phuene/archive/2007/05/03/code-coverage-instrumentation.aspx code coverage instrumentation]&amp;quot;). Dieser Code gibt nach dem Testen z.B. aus, welche Programmzeilen von keinem existierenden Test aufgerufen worden sind. Wenn der ausgeführte Code sehr stark von den Daten abhängt (z.B. bei interaktiven Programmen), kann es sehr schwierig sein, die ''coverage'' auf andere Weise festzustellen.&lt;br /&gt;
:* absichtlich Bugs einbauen (die automatisch wieder abgeschaltet werden, wenn das Testen vorbei ist). Durch diese &amp;quot;[http://en.wikipedia.org/wiki/Fault_injection fault injection]&amp;quot; kann man herausfinden, ob die Tests mächtig genug sind, vorhandene Bugs zu finden.&lt;br /&gt;
&lt;br /&gt;
====Prinzipien für die Generierung von Testdaten====&lt;br /&gt;
&lt;br /&gt;
;Prinzip der Regressionstests (&amp;quot;[http://en.wikipedia.org/wiki/Regression_testing Regression testing]&amp;quot;): Häufig werden Tests während der Programmentwicklung verwendet, um einen Algorithmus zu debuggen. Sobald der Algorithmus aber funktioniert werden die Tests gelöscht, denn sie werden ja jetzt nicht mehr gebraucht. Dies ist ein schwerwiegender ''Fehler'': Jedes erfolgreiche Programm muss früher oder später weiterentwickelt werden (zumindest die Anpassung an eine neue Betriebssystemversion ist ab und zu notwendig). Jede Änderung birgt aber die Gefahr, dass sich neue Bugs in bisher funktionierenden Code einschleichen. Man sollte deshalb alle Tests aufheben und in einer ''test suite'' sammeln. Durch diese &amp;quot;regression tests&amp;quot; kann man nach jeder Änderung feststellen, ob die alte Funktionalität noch intakt ist, und gegebenenfalls die letzte Änderung einfach rückgängig machen. Tut man dies nicht, kann die Gefahr von unbeabsichtigten destruktiven Änderungen so groß werden, dass das Programm gar nicht mehr weiterentwickelt werden kann. Dies wird drastisch durch den bekannten Spruch &amp;quot;never change a running program&amp;quot; ausgedrückt.&lt;br /&gt;
&lt;br /&gt;
;Prinzip der äquivalenten Eingaben (Domain Partitioning oder Equivalence Partitioning) [http://en.wikipedia.org/wiki/Equivalence_partitioning]: Für ähnliche Eingaben verhält sich ein Algorithmus normalerweise ähnlich, und es hat keinen Sinn, alle diese Eingaben zu testen. Statt dessen teilt (partitioniert) man die Eingabedomäne in Äquivalenzklassen, die vom Algorithmus im wesentlichen gleich behandelt werden. Im obigen Beispiel der Wurzelberechnung ergeben sich zwei Klassen aus der Spezifikation: die negativen Zahlen (für die die Wurzel undefiniert ist und deshalb ein Fehler signalisiert werden muss) und die nicht-negativen Zahlen. Wenn man auch den Quellcode kennt (gray-box testing), kann man die Eingaben oft feiner unterteilen. Z.B. werden häufig unterschiedliche Algorithmen für kleine und für große Eingaben benutzt. Viele Quicksort-Implementationen verwenden beispielsweise für Arrays mit höchstens vier Elementen ein explizites Sortierverfahren, für Arrays der Länge 5 bis 25 selection sort, und erst für größere Arrays das eigentliche Quicksort. Aus der Einteilung der Eingabedomäne ergeben sich zwei wichtige Regeln für die Wahl der Testdaten:&lt;br /&gt;
:* Aus jeder Äquivelenzklasse wählt man mindestens einen typischen Vertreter, um das normale Verhalten des Algorithmus in jedem Fall zu testen.&lt;br /&gt;
:* Aus jeder Äquivelenzklasse wählt man Randwerte, weil gerade bei diesen Werten am häufigsten Fehler gemacht werden. Im obigen Wurzelbeispiel ist der Randwert die Null, die in der Tat in einer Version des Algorithmus zu einem &amp;lt;TT&amp;gt;ZeroDivisionError&amp;lt;/tt&amp;gt; geführt hat. Andere typische Randfehler sind, dass Randelemente dem falschen Algorithmenzweig zugeordnet werden (z.B. wenn bei unserem Wurzelbeispiel die Abfrage am Anfang &amp;lt;tt&amp;gt;if x &amp;lt;= 0:&amp;lt;/tt&amp;gt; statt &amp;lt;tt&amp;gt;if x &amp;lt; 0:&amp;lt;/tt&amp;gt; gewesen wäre), dass Schleifen um einen Index zu spät beginnen oder zu früh abbrechen (&amp;quot;[http://en.wikipedia.org/wiki/Off-by-one_error Off-by-one errors]&amp;quot;), oder dass ein seltener Randfall gar nicht implementiert ist und einfach zum Absturz führt.&lt;br /&gt;
&lt;br /&gt;
;Prinzip, den Fehler zu reproduzieren (Failure Reproduction): Wenn ein Bug gemeldet wird, welches die Tests bisher übersehen haben, fügt man einen Test hinzu, der dieses Bug findet. Im Zusammenhang mit regression tests ist damit sichergestellt, dass dasselbe Bug nicht noch einmal auftreten kann.&lt;br /&gt;
&lt;br /&gt;
;Prinzip der Code Coverage [http://en.wikipedia.org/wiki/Code_coverage]: Hier stellt man sicher, dass tatsächlich der gesamte Code (oder ein vorher festgelegter hoher Prozentsatz) getestet wurde. Gerade bei komplizierten interaktiven Programmen ist diese &amp;quot;code coverage&amp;quot; mitunter nicht leicht zu erreichen, weil manche Programmteile nur bei sehr seltenen oder obskuren Eingaben ausgeführt werden. Eine minimale code coverage erreicht man allerdings bereits, wenn man in einem black-box-Test die Testdaten nach dem Prinzip der äquivalenten Eingaben auswählt, weil dann aus jeder Äquivalenzklasse mindestens ein Vertreter getestet wird. Im Allgemeinen muss man aber den Quellcode zumindest kennen (gray-box-Test), um geeignete Testdaten für code coverage zu identifizieren. Code coverage kann in verschiednen Graden angestrebt werden&lt;br /&gt;
:* Function coverage: Jede Funktion eines Programms sollte mindestens einmal aufgerufen werden.&lt;br /&gt;
:* Statement coverage: Jedes Statement (d.h. im wesentlichen jede Programmzeile) sollte mindestens einmal ausgeführt werden. Im obigen Wurzelbeispiel erfordert dies, dass z.B. mindestens einmal eine negative Zahl getestet wird, um die Exception zu prüfen.&lt;br /&gt;
:* Condition coverage: Jede Bedingung (explizit in &amp;lt;tt&amp;gt;if&amp;lt;/tt&amp;gt;-Bedingungen, implizit in den Abbruchbedingungen von &amp;lt;tt&amp;gt;for&amp;lt;/tt&amp;gt;- und &amp;lt;tt&amp;gt;while&amp;lt;/tt&amp;gt;-Schleifen) sollte mindestens einmal mit dem Ergebnis &amp;lt;tt&amp;gt;True&amp;lt;/tt&amp;gt; und einmal mit dem Ergebnis &amp;lt;tt&amp;gt;False&amp;lt;/tt&amp;gt; durchlaufen werden. Im Wurzelbeispiel haben wir die Eingabe &amp;lt;tt&amp;gt;x = 4&amp;lt;/tt&amp;gt; gewählt, damit die &amp;lt;tt&amp;gt;while&amp;lt;/tt&amp;gt;-Schleife auch einmal beim ersten Aufruf sofort &amp;lt;tt&amp;gt;False&amp;lt;/tt&amp;gt; liefert.&lt;br /&gt;
:* Path coverage: Jeder Programmpfad (d.h. jede Kombination von Wahrheitswerten bei allen Bedingungen) sollte einmal ausgeführt werden. Dies ist im Allgemeinen unerreichbar, weil es unendlich viele, oder zumindest zu viele verschiedene Pfade gibt.&lt;br /&gt;
:Die Qualität der Tests steigt, wenn eine hohe Coverage (am besten 100%) erreicht wird, und/oder man eine mächtigere Art von Coverage fordert.&lt;br /&gt;
&lt;br /&gt;
;Prinzip der erschöpfenden Tests: Wenn ein Algorithmus nur wenige mögliche Eingaben hat, kann man sämtliche Eingaben testen. Bei sehr wichtigen Algorithmen kann das auch dann noch sinnvoll sein, wenn es relativ viele mögliche Eingaben gibt. In den meisten Fällen ist es jedoch zu aufwändig.&lt;br /&gt;
&lt;br /&gt;
;Prinzip der vollständigen Paarung (Pair-wise coverage) [http://citeseer.ist.psu.edu/78354.html]: Wenn ein Algorithmus N Eingabeparameter hat, und jeder Parameter hat K&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; mögliche Werte, müssen bei der erschöpfenden Suche K&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;*...*K&amp;lt;sub&amp;gt;N&amp;lt;/sub&amp;gt; Kombinationen getestet werden. Beschränkt man sich in jedem Parameter auf typische Werte und Randwerte jeder Äquivalenzklasse, kann man K&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; zwar drastisch reduzieren, aber das Produkt K&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;*...*K&amp;lt;sub&amp;gt;N&amp;lt;/sub&amp;gt; wird immer noch sehr groß (bei 4 Parametern und nur 3 möglichen Werten pro Parameter hat man bereits 3&amp;lt;sup&amp;gt;4&amp;lt;/sup&amp;gt;=81 mögliche Kombinationen). Sei v&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt; der j-te Wert des Parameters i. Anstatt zu versuchen, alle Kombinationen zu testen, kann man fordern, dass zumindest alle möglichen Paare v&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt; und v&amp;lt;sub&amp;gt;mj&amp;lt;/sub&amp;gt; (i&amp;amp;ne;m) in mindestens einem Test vorkommen. Gibt es nur zwei Parameter, gewinnt man durch diese Einschränkung natürlich nichts, denn man muss mindestens K&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;*K&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; Tests durchführen. Hat man jedoch 3 Parameter, kann man mit weniger Tests auskommen als zuvor, da jeder Test bis zu drei verschiedene Paarungen abdecken kann (eine für den ersten und zweiten Parameter, eine für den ersten und dritten, eine für den zweiten und dritten). Bei vier Parametern werden sogar sechs Paarungen pro Test abgearbeitet usw. Die Theorie des &amp;quot;experimental design&amp;quot; beschreibt nun, wie man systematisch alle möglichen Paarungen mit möglichst wenigen Tests erzeugt. Es stellt sich heraus, dass man alle Paarungen von 3, 4 oder mehr Parametern oft mit genauso vielen Tests erzeugen kann wie bei 2 Parametern nötig wären. Dazu verwendet man die Methode der [http://en.wikipedia.org/wiki/Latin_square Latin Squares].  Wir beschreiben diese Methode für den einfachen Fall von 3 möglichen Werten pro Parameter.&lt;br /&gt;
&lt;br /&gt;
:Ein Latin Square der Größe 3 ist eine 3x3 Matrix, deren Einträge die Zahlen 1...3 sind, und zwar so, dass jede Zahl genau einmal in jeder Zeile und Spalte vorkommt (ähnlich wie beim Sudoku). Eine mögliche Matrix ist z.B.&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;P=\begin{pmatrix}1 &amp;amp; 2 &amp;amp; 3 \\&lt;br /&gt;
                      2 &amp;amp; 3 &amp;amp; 1 \\&lt;br /&gt;
                      3 &amp;amp; 1 &amp;amp; 2\end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
:Man bildet jetzt 9 Kombinationen der Zahlen 1...3, indem man zeilenweise durch die Matrix P geht, und den Zeilenindex (die Nummer der aktuellen Zeile) als erste Zahl, den Spaltenindex als zweite Zahl, und den Eintrag an der aktuallen Position als dritte Zahl verwendet. Man erhält&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;0&amp;quot; cellpadding=&amp;quot;7&amp;quot; align=&amp;quot;center&amp;quot;&lt;br /&gt;
|-align=&amp;quot;center&amp;quot; &lt;br /&gt;
|&lt;br /&gt;
! Komb. 1&lt;br /&gt;
! Komb. 2&lt;br /&gt;
! Komb. 3&lt;br /&gt;
! Komb. 4&lt;br /&gt;
! Komb. 5&lt;br /&gt;
! Komb. 6&lt;br /&gt;
! Komb. 7&lt;br /&gt;
! Komb. 8&lt;br /&gt;
! Komb. 9&lt;br /&gt;
|-&lt;br /&gt;
!Zahl 1 (Zeilenindex)&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|-&lt;br /&gt;
! Zahl 2 (Spaltenindex)&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|-&lt;br /&gt;
! Zahl 3 (aktueller Matrixeintrag von P)&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
:Diese Tabelle bestimmt, welcher Wert in jedem Test für jeden Parameter verwendet wird. Z.B. wird der erste Test mit v&amp;lt;sub&amp;gt;11&amp;lt;/sub&amp;gt; (erster Wert des ersten Parameters), v&amp;lt;sub&amp;gt;21&amp;lt;/sub&amp;gt; (erster Wert des zweiten Parameters), v&amp;lt;sub&amp;gt;31&amp;lt;/sub&amp;gt; (erster Wert des dritten Parameters) aufgerufen&lt;br /&gt;
       assertEqual( foo(v11, v21, v31), foo_reference1)&lt;br /&gt;
(reference1 ist das korrekte Referenz-Ergebnis für diese Prameterbelegung). Der letzte Test hat die Parameter v&amp;lt;sub&amp;gt;13&amp;lt;/sub&amp;gt;, v&amp;lt;sub&amp;gt;23&amp;lt;/sub&amp;gt;, v&amp;lt;sub&amp;gt;32&amp;lt;/sub&amp;gt;&lt;br /&gt;
       assertEqual( foo(v13, v23, v32), foo_reference9)&lt;br /&gt;
:Man überzeugt sich leicht, dass diese 9 Tests jede mögliche Paarung genau einmal enthalten. Hat der Algorithmus 4 Parameter, benötigt man einen zweiten Latin Square, der zum ersten orthogonal ist. Zwei Latin Squares P und Q heißen orthogonal, wenn alle Paare c&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt;=(P&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt;, Q&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt;) eindeutig sind, d.h. es gilt c&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt;&amp;amp;ne;c&amp;lt;sub&amp;gt;kl&amp;lt;/sub&amp;gt; falls i&amp;amp;ne;k und j&amp;amp;ne;l. Ein zu dem obigen P orthogonales Q ist z.B.&lt;br /&gt;
:&amp;lt;math&amp;gt;Q=\begin{pmatrix}1 &amp;amp; 2 &amp;amp; 3 \\&lt;br /&gt;
                        3 &amp;amp; 1 &amp;amp; 2 \\&lt;br /&gt;
                        2 &amp;amp; 3 &amp;amp; 1\end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
: Jetzt bildet man Kombinationen aus 4 Zahlen, indem man zur obigen Tabelle noch eine vierte Zeile hinzufügt, die die aktuellen Einträge von Q für den jeweiligen Zeilen- und Spaltenindex enthält:&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;0&amp;quot; cellpadding=&amp;quot;7&amp;quot; align=&amp;quot;center&amp;quot;&lt;br /&gt;
|-align=&amp;quot;center&amp;quot; &lt;br /&gt;
|&lt;br /&gt;
! Komb. 1&lt;br /&gt;
! Komb. 2&lt;br /&gt;
! Komb. 3&lt;br /&gt;
! Komb. 4&lt;br /&gt;
! Komb. 5&lt;br /&gt;
! Komb. 6&lt;br /&gt;
! Komb. 7&lt;br /&gt;
! Komb. 8&lt;br /&gt;
! Komb. 9&lt;br /&gt;
|-&lt;br /&gt;
!Zahl 1 (Zeilenindex)&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|-&lt;br /&gt;
! Zahl 2 (Spaltenindex)&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|-&lt;br /&gt;
! Zahl 3 (aktueller Matrixeintrag von P)&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|-&lt;br /&gt;
! Zahl 4 (aktueller Matrixeintrag von Q)&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
:Es sind immer noch nur 9 Tests nötig, um alle Paarungen zu erzeugen. Der erste und letzte Test sind nun:&lt;br /&gt;
       assertEqual( bar(v11, v21, v31, v41), bar_reference1)&lt;br /&gt;
       ...&lt;br /&gt;
       assertEqual( bar(v13, v23, v32, v41), bar_reference9)&lt;br /&gt;
:Die Methode der Latin Squares  funktioniert auch, wenn mehr als 3 Belegungen für jeden Parameter möglich sind, und wenn es mehr als 4 Parameter gibt. Für die Einzelheiten verweisen wir auf die Literatur, z.B. [http://citeseer.ist.psu.edu/78354.html], [http://en.wikipedia.org/wiki/Latin_square]. Empirische Untersuchungen haben ergeben, dass die Methode der vollständigen Paarung oft über 90% der Fehler in einem Programm finden kann.&lt;br /&gt;
&lt;br /&gt;
[[Effizienz|Nächstes Thema]]&lt;/div&gt;</summary>
		<author><name>Jschleic</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Main_Page&amp;diff=4434</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=4434"/>
				<updated>2008-09-24T10:48:07Z</updated>
		
		<summary type="html">&lt;p&gt;Jschleic: Undo revision 4422 by 87.118.120.241 (Talk) spam&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 2008&lt;br /&gt;
&lt;br /&gt;
Die Vorlesung findet '''mittwochs''' um 11:15 Uhr in INF 227, HS 2 und '''donnerstags''' um 11:15 Uhr in INF 308, HS 2 statt. &lt;br /&gt;
&lt;br /&gt;
=== Klausur und Nachprüfung ===&lt;br /&gt;
&lt;br /&gt;
Die '''Abschlussklausur''' findet am Mittwoch, dem 23.7.2008 von 10:00 bis 12:30 Uhr im HS1, INF 227 (KIP) statt. (Hinweis: Sie benötigen einen Lichtbildausweis, um sich bei der Klausur zu indentifizieren!)&lt;br /&gt;
&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;
&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. Im einzelnen können erworben werden:&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;
&lt;br /&gt;
=== Übungsbetrieb ===&lt;br /&gt;
* Termine der Übungsgruppen:&lt;br /&gt;
** Mo 11:00 - 13:00 Uhr, INF 350 (Otto-Meyerhof-Zentrum, Seiteneingang), Raum 014 (Tutor: Rahul Nair, [mailto:rnair(at)gmx(punkt)de rnair (at) gmx (punkt) de])&lt;br /&gt;
** Di 11:00 - 13:00 Uhr, INF 350 (Otto-Meyerhof-Zentrum, Seiteneingang), Raum 014 (Tutor: Thomas Gerlach, [mailto:gerlach@kip.uni-heidelberg.de gerlach@kip.uni-heidelberg.de])&lt;br /&gt;
** Mi 14:00 - 16:00 Uhr, '''neu: INF 327, Raum SR 5''' (Tutor: Christoph Sommer, [mailto:christoph.sommer@iwr.uni-heidelberg.de christoph.sommer@iwr.uni-heidelberg.de])  &lt;br /&gt;
** Do 14:00 - 16:00 Uhr, INF 294, Raum -113 (im Untergeschoss, Tutor: Daniel Kondermann, [mailto:daniel.kondermann@iwr.uni-heidelberg.de daniel.kondermann@iwr.uni-heidelberg.de])&lt;br /&gt;
* [[Main Page#Übungsaufgaben|Übungsaufgaben]] (Übungszettel mit Abgabetermin, Musterlösungen)&lt;br /&gt;
* [[Media:Punktestand.pdf|aktueller Punktestand]] (PDF, anonymisiert, so aktuell, wie von den Tutoren an mich übermittelt -- UK)&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. Es gibt verschiedene Möglichkeiten, Zusatzpunkte zu erlangen (Bonusaufgaben, Anfertigung der Wiki-Seiten, gute Mitarbeit in den Übungen).&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]] (9.4.2008) &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]] (10.4.2008)&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]] (16. und 17.4.2008)&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]] (23. - 30.4.2008)&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]] (30.4. - 14.5.2008)&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]] (14. - 21.5.2008)&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]] (28.5.2008)&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]] (29.5.und 4.6.2008)&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]] (5.6.2008)&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]] (11.6.2008)&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]] (12. bis 2.7.2008)&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 9.7.2008)&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 16.7.2008)&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]] (16. und 17.7.2008)&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;
&amp;lt;!-----# [[Quantum computing]] (17.7.2008)----&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Übungsaufgaben ==&lt;br /&gt;
&lt;br /&gt;
(im PDF Format). Die Abgabe erfolgt am angegebenen Tag bis 11: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 17.4.2008) und [[Media:Übung-1-Musterlösung.pdf|Musterlösung]]&lt;br /&gt;
#* Python-Tutorial&lt;br /&gt;
#* Sieb des Eratosthenes&lt;br /&gt;
#* Wert- und Referenzsemantik&lt;br /&gt;
&amp;lt;!--------------------&amp;gt;&lt;br /&gt;
# [[Media:Übung-2.pdf|Übung]] (Abgabe 24.4.2008) sowie Musterlösungen für [[Media:muster_blatt2-aufgabe1.pdf|Aufgabe 1]] und [[Media:muster_blatt2-aufgabe2.pdf|Aufgabe 2]]&lt;br /&gt;
#* Sortieren: Implementation und Geschwindigkeitsvergleich (Diagramme in Abhängigkeit von Problemgröße)&lt;br /&gt;
#* Entwicklung eines effizienten Algorithmus: Bruchfestigkeit von Gläsern&lt;br /&gt;
&amp;lt;!--------------------&amp;gt;&lt;br /&gt;
# [[Media:Übung-3.pdf|Übung]] ('''neuer Abgabetermin''' 7.5.2008) und [[Media:Übung-3-Musterlösung.pdf|Musterlösung]]&lt;br /&gt;
#* Experimente zur Effektivität von Unit Tests&lt;br /&gt;
#* Deque-Datenstruktur: Vor- und Nachbedingungen der Operationen, Implementation und Unit Tests&lt;br /&gt;
&amp;lt;!--------------------&amp;gt;&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;!--------------------&amp;gt;&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;!--------------------&amp;gt;&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;!--------------------&amp;gt;&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;!--------------------&amp;gt;&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;!--------------------&amp;gt;&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;!--------------------&amp;gt;&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;!--------------------&amp;gt;&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;!--------------------&amp;gt;&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;/div&gt;</summary>
		<author><name>Jschleic</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=2537</id>
		<title>Suchen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=2537"/>
				<updated>2008-07-22T13:44:32Z</updated>
		
		<summary type="html">&lt;p&gt;Jschleic: /* Komplexitätsanalyse */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;!--Hallo, ich kenne leider deinen Namen nicht, ich habe den Wiki-Eintrag von letztem Donnerstag gemacht.Da das Thema 'Dynamisches Array' noch Thema von letzter Woche war, hab ich die Wiederholung der amortisierten Kosten von diesem Mittwoch noch bei Effizienz eingetragen, du musst es also nicht noch mal in deinem Wiki eintragen. Du kannst dir aber gerne mal anschauen was ich geschrieben habe, vielleicht fallen dir noch ein paar Sachen ein, die man hätte besser machen können. lg, Franziska.--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Das Suchen ist eine grundlegende Operation in der Informatik. Viele Probleme in der Informatik können auf Suchaufgaben zurückgeführt werden.&lt;br /&gt;
&lt;br /&gt;
Gemeint ist mit Suchen das Wiederauffinden eines Datensatzes aus einer Menge von früher gespeicherten Datensätzen, oder das Auffinden einer bestimmten Lösung in einem (potentiell großen) Suchraum möglicher Lösungen. Ein paar einleitende Worte zum Suchproblem findet man [http://de.wikipedia.org/wiki/Suche hier].&lt;br /&gt;
&lt;br /&gt;
== Überblick über verschiedene Suchmethoden ==&lt;br /&gt;
&lt;br /&gt;
Um sich der Vielseitigkeit des Suchproblems bewusst zu werden, ist es sinnvoll, sich einen Überblick über verschiedene Suchmethoden zu verschaffen. &lt;br /&gt;
&lt;br /&gt;
Hier sei auch auf einen bereits existierenden Wikipedia-Artikel zu [http://de.wikipedia.org/wiki/Suchverfahren Suchverfahren] verwiesen.&lt;br /&gt;
&lt;br /&gt;
Allen gemeinsam ist die grundlegende Aufgabe, ein Datenelement mit bestimmten Eigenschaften aus einer großen Menge von Datenelementen zu selektieren.&lt;br /&gt;
Dies kann, natürlich ohne jeden Anspruch auf Vollständigkeit, nach einer der jetzt diskutierten Methoden geschehen:&lt;br /&gt;
&lt;br /&gt;
* '''Schlüsselsuche''': meint das Suchen von Elementen mit bestimmtem Schlüssel; ein klassisches Beispiel wäre das Suchen in einem Wörterbuch,  die Schlüssel entsprechen hier den Wörtern, die Datensätze wären die zu den Wörtern gehörigen Eintragungen.&lt;br /&gt;
&lt;br /&gt;
* '''Bereichssuche''': Im Allgemeinen meint die Bereichssuche in n-Dimensionen die Selektion von Elementen mit Eigenschaften aus einem bestimmten n-dimensionalen Volumen. Im eindimensionalen Fall will man alle Elemente finden, deren Eigenschaft(en) in einem bestimmten Intervall liegen. Die Verallgemeinerung auf n-Dimensionen ist offensichtlich. Ein Beispiel für die Bereichssuche in einer 3D-Kugel wäre ein Handy mit Geolokalisierung, welches alle Restaurants in einem Umkreis von 500m findet. Lineare Ungleichungen werden graphisch durch [http://de.wikipedia.org/wiki/Hyperebene Hyperebenen] repräsentiert. In 2D sind diese Hyperebenen Geraden. Die Ungleichungen können dann den Lösungsraum in irgendeiner Form begrenzen.&lt;br /&gt;
&lt;br /&gt;
* '''Ähnlichkeitssuche''': Finde Elemente, die gegebenen Eigenschaften möglichst ähnlich sind. Ein prominentes Beispiel ist Google (=Ähnlichkeit zwischen Suchbegriffen und Dokumenten) oder das Suchen des nächstengelegenen Restaurants (Ähnlichkeit zwischen eigener Position und Position des Restaurants). Ein wichtiger Spezialfall ist die ''nächste-nachbar Suche''.&lt;br /&gt;
&lt;br /&gt;
* '''Graphensuche''': Hier wäre beispielsweise das Problem optimaler Wege zu nennen (Navigationssuche). Dieser Punkt wird später im Verlauf der Vorlesung noch einmal aufgegriffen werden.&lt;br /&gt;
&lt;br /&gt;
Im jetzt folgenden wird nur noch die ''Schlüsselsuche'' betrachtet werden.&lt;br /&gt;
&lt;br /&gt;
==Sequentielle Suche==&lt;br /&gt;
&lt;br /&gt;
Die ''sequentielle'' oder ''lineare'' Suche ist die einfachste Methode, einen Datensatz zu durchsuchen. Hierbei wird ein Array beispielsweise sequentiell von vorne nach hinten durchsucht. Ein prinzipieller Vorteil der Methode ist, dass auf der Eigenschaft der Datenelemente, nach denen das Array durchsucht wird, keine Ordnung im Sinne von &amp;gt; oder &amp;lt; definiert zu sein braucht, lediglich die Identität (==) muss feststellbar sein. Der folgende Python-Code zeigt, wie man sequentielle Suche einsetzen kann:&lt;br /&gt;
&lt;br /&gt;
 a = ... # array mit den zu durchsuchenden Elementen&lt;br /&gt;
 &lt;br /&gt;
 foundIndex = sequentialSearch(a, key) &lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq &amp;lt;/math&amp;gt; foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
Wir verwenden hier die Konvention, dass der zugehörige Arrayindex zurückgegeben wird, falls ein Element mit dem Schlüssel &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; gefunden wird (falls es mehrere solche Elemente gibt, wird das erste zurückgegeben). Das Ergebnis &amp;lt;tt&amp;gt;-1&amp;lt;/tt&amp;gt; signalisiert hingegen, dass kein solches Element gefunden wurde. Die Funktion &amp;lt;tt&amp;gt;sequentialSearch&amp;lt;/tt&amp;gt; kann folgendermaßen implementiert werden:&lt;br /&gt;
&lt;br /&gt;
 def sequentialSearch(a, key):&lt;br /&gt;
    for i in range(len(a)):&lt;br /&gt;
        if a[i] == key:  # bzw. allgemeiner a[i].key == key &lt;br /&gt;
            return i&lt;br /&gt;
    return -1&lt;br /&gt;
&lt;br /&gt;
Wir wollen jetzt die Komplexität dieses Algorithmus bestimmen, wobei die Problemgröße durch &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt; gegeben ist. &lt;br /&gt;
&lt;br /&gt;
Dabei nimmt man an, dass der Vergleich in der inneren Schleife (&amp;lt;tt&amp;gt;a[i] == key&amp;lt;/tt&amp;gt;) jeweils &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt; ist (diese Annahme könnte verletzt sein, wenn der Vergleichsoperator eine komplizierte Berechnung mit höherer Komplexität ausführen muss). Bei einer erfolglosen Suche wird dieser Vergleich in der for-Schleife N-mal durchgeführt (&amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt;), bei einer erfolgreichen Suche im Mittel (N/2)-mal (ebenfalls &amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt;). Nach der Verschachtelungsregel erhält man also eine gesamte Komplexität von &amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Der Name ''lineare'' Suche rührt von diesem linearen Anwachsen der Komplexität mit der Arraygröße her.&lt;br /&gt;
&lt;br /&gt;
==Binäre Suche==&lt;br /&gt;
&lt;br /&gt;
Wie wir weiter unten zeigen werden, gestattet es diese Suchmethode, die Gesamtdauer der Suche in großen Datensätzen beträchtlich zu verringern. Die Methode beruht auf dem [http://de.wikipedia.org/wiki/Divide_and_Conquer Divide and Conquer-Prinzip], wobei die Suche in jedem Schritt rekursiv auf eine Hälfte des Datensatzes eingeschränkt wird. Weitere Details zur Methode sind [http://de.wikipedia.org/wiki/Bin%C3%A4re_Suche hier] zu finden. &lt;br /&gt;
&lt;br /&gt;
Die Methode ist nur dann anwendbar beziehungsweise effektiv, wenn folgendes gilt:&lt;br /&gt;
&lt;br /&gt;
# Auf der Eigenschaft der Daten, die zur Suche verwendet wird, ist eine Ordnung im Sinne von &amp;lt; oder &amp;gt; definiert.&lt;br /&gt;
# Wir wollen uns auf Datensätze beschränken, die schon fertig aufgebaut sind, in die also keine neuen Elemente mehr eingefügt werden, wenn man mit dem Suchen beginnt. Ist dies nicht der Fall, müsste nach jeder Einfügung das Array neu sortiert werden (unter diesen Umständen wäre die Verwendung eines [[Suchen#Suchb.C3.A4ume|Suchbaumes]] geschickter). &lt;br /&gt;
&lt;br /&gt;
Im Unterschied zur sequenziellen Suche müssen wir jetzt das Array sortieren bevor die Suchfunktion aufgerufen werden kann:&lt;br /&gt;
&lt;br /&gt;
 a = [...,...]     # array&lt;br /&gt;
 a.sort()   # sortiere über Ordnung des Schlüssels&lt;br /&gt;
 foundIndex = binSearch(a, key, 0, len(a))  # (Array, Schlüssel, von wo bis wo suchen im Array)&lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq&amp;lt;/math&amp;gt;  foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
Der folgende Algorithmus zeigt eine beispielhafte Implementierung der Methode:&lt;br /&gt;
&lt;br /&gt;
 def binSearch(a, key, start, end):  # start ist 1. Index, end ist letzter Index + 1&lt;br /&gt;
    size = end - start   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if size &amp;lt;= 0:   # Bereich leer?  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return -1   # also nichts gefunden, &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    center = (start + end)/2   # Integer Division (d.h. Ergebnis wird abgerundet, wichtig für ganzzahlige Indizes) &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if a[center] == key:  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return center  # Schlüssel gefunden, &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    elif a[center] &amp;lt; key:  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return binSearch(a, key, center + 1, end)  # Rekursion in die rechte Teilliste&lt;br /&gt;
    else:&lt;br /&gt;
        return binSearch(a, key, start, center)  # Rekursion in die linke Teilliste&lt;br /&gt;
&lt;br /&gt;
Zur Berechnung der Komplexität dieses Algorithmus vernachlässigen wir zunächst den Aufwand, den die Sortierung verursacht (wir diskutieren unten, wann dies nicht zulässig ist). Wir setzen &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Im obigen Code ist zu erkennen, dass fast alle Anweisungen des Algorithmus die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;. Nach der Sequenzregel hat auch deren Hintereinanderausführung die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;.  Es bleibt die Komplexität der Rekursion zu berechnen. Die gesamte Komplexität des Algorithmus (jetzt als Funktion f bezeichnet) setzt sich zusammen aus den oben erwähnten &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;-Anweisungen sowie der Rekursion auf einem Teilarray der halben Größe &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f(N) = \mathcal{O}(1) + f(N/2) = \mathcal{O}(1) + \mathcal{O}(1) + f(N/4) = ... = \underbrace{\mathcal{O}(1) + ... + \mathcal{O}(1) + \underbrace{f(0)}_{\mathcal{O}(1)\, \rightarrow \,\mathrm{size-Abfrage}}}_{n+1 \,\mathrm{Terme}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Zur Vereinfachung nehmen wir an &amp;lt;math&amp;gt; N = 2^n &amp;lt;/math&amp;gt;, so dass gilt&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \rightarrow f(N) = \mathcal{O}(1) \cdot \mathcal{O}(n+1) = \mathcal{O}(n) = \mathcal{O}(\lg N) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Für große Datenmengen ist die ''binäre Suche'' also weit effizienter als die ''lineare Suche''. Verdoppelt sich beispielsweise die zu durchsuchende Datenmenge, so verdoppelt sich der Aufwand für die ''sequentielle Suche'' - bei der ''binären Suche'' hingegen benötigt man lediglich eine zusätzliche Vergleichsoperation. &lt;br /&gt;
&lt;br /&gt;
Für kleine Daten (&amp;lt;math&amp;gt; N = 4,\, 5 &amp;lt;/math&amp;gt;) ist die ''sequentielle Suche'' jedoch schneller als die ''binäre Suche'', da hier die rekursiven Funktionsaufrufe teurer als das Mehr an Vergleichen sind. Ein anderer ungünstiger Fall ist gegeben, wenn nur sehr wenige Suchanfragen erfolgen (weniger als &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; viele). Dann wird der Aufwand durch das Sortieren des Arrays dominiert, ist also &amp;lt;math&amp;gt;\mathcal{O}(N \lg N) &amp;lt;/math&amp;gt;. Auch dann ist sequentielle Suche vorzuziehen.&lt;br /&gt;
&lt;br /&gt;
Eine relativ einfache Möglichkeit, die ''binäre Suche'' zu verbessern, ist die sogenannte ''Interpolationssuche''. Hierbei wird die neue Position für die Suche, also die Mitte des Arrays, durch eine Schätzung ersetzt, die angibt, wo sich der Schlüssel innerhalb des Arrays befinden könnte. Bei der Suche in einem Telefonbuch nach dem Namen Zebra würde man ja auch nicht in der Mitte anfangen. Näheres hierzu im Buch von ''Sedgewick''.&lt;br /&gt;
&lt;br /&gt;
Um sich den Algorithmus der ''binären Suche'' klar zu machen, ist es instruktiv, sich die folgende Tabelle genauer anzusehen, die die sukzessive Belegung der Variablen bei verschiedenen Anfragen beschreibt. Die Testfälle wurden nach dem Prinzip des ''domain partitioning'' gewählt. Das zugehörige Array hat die Einträge&lt;br /&gt;
&lt;br /&gt;
 a = [2, 3, 4, 5, 6]&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align:center&amp;quot; border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;5&amp;quot; cellspacing=&amp;quot;0&amp;quot; &lt;br /&gt;
! gesuchter key   !!  start      !! end  !! size !! center !! return &amp;lt;br/&amp;gt; (-1 oder index)  !! Kommentare  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 4     ||0            || 5    ||  5   || 2   ||  2         || gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 2     || 0           || 5    ||  5   || 2   ||            || linker Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||           &lt;br /&gt;
|-&lt;br /&gt;
|       ||  0          || 1    ||  1   || 0   ||  0         || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 1     ||0            || 5    ||  5   || 2   ||            || links außerhalb&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 1    ||  1   || 0   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 0    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 6     ||0            || 5    ||  5   || 2   ||            || rechter Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    || 2    || 4   || 4          || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 5     ||0            || 5    ||  5   || 2   ||            || typischer Fall&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||3            || 5    ||  2   || 4   ||            ||  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       || 3           || 4    ||  1   || 3   || 3          || gefunden&lt;br /&gt;
|- &lt;br /&gt;
| 7     ||0            || 5    ||  5   || 2   ||            || rechts außerhalb        &lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    ||  2   || 4   ||            ||&lt;br /&gt;
|-&lt;br /&gt;
|       ||5            || 5    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Suchbäume ==&lt;br /&gt;
&lt;br /&gt;
Effiziente Suchalgorithmen kann man elegent mit Hilfe von Binärbäumen realisieren. Eine kurze Einführung in Binärbäume findet man [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum hier]. Die Skizze erläutert wichtige Begriffe:&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum.png|text-top|300x300px|Zur Illustration von Bäumen]]&lt;br /&gt;
&lt;br /&gt;
Bäume sind zweidimensional verkettete Strukturen. Sie gehören zu den fundamentalen Datenstrukturen in der Informatik. Da man in Bäumen nicht nur Daten speichern kann, sondern auch relevante Beziehungen der Daten untereinander, festgelegt über eine Ordnung auf der vergleichenden Dateneigenschaft (''Schlüssel''), eignen sich Bäume also insbesondere, um gesuchte Daten schnell wieder auffinden zu können.&lt;br /&gt;
&lt;br /&gt;
Ein ''Binärbaum'' wie oben skizziert besteht aus einer Menge von ''Knoten'', die untereinander durch ''Kanten'' verbunden sind. Jeder Knoten hat einen linken und einen rechten Unterbaum, der auch leer sein kann (in Python ließe sich dies mit ''None'' implementieren). Führt eine Kante von Knoten A zu Knoten B, so heißt A Vater von B und B Kind von A. Es gibt genau einen Knoten ohne Vater, den man ''Wurzel'' nennt. Knoten ohne Kinder heißen ''Blätter''.&lt;br /&gt;
&lt;br /&gt;
Ein ''Suchbaum'' hat zusätzlich die Eigenschaft, dass die Schlüssel jedes Knotens sortiert sind: &lt;br /&gt;
;Suchbaumbedingung: Für jeden Knoten des Binärbaumes gilt: Alle Schlüssel im linken Unterbaum sind kleiner als der Schlüssel des gegebenen Knotens, alle Schlüssel im rechten Unterbaum sind größer. Wir wollen hierbei annehmen, dass jeder Schlüssel pro Datensatz nur einmal vorkommt, da sich sonst die &amp;gt;- oder &amp;lt;-Relation nicht mehr strikt erfüllen ließe.&lt;br /&gt;
&lt;br /&gt;
Um die Verwendung eines Suchbaums zu motivieren, wollen wir von zwei Annahmen ausgehen:&lt;br /&gt;
# Einfügen und Suchen im Baum wechseln sich ab. (Wenn das Suchen erst beginnt, nachdem alle Einfügungen erfolgt sind, wäre ein dynamisches Array mit [[Suchen#Bin.C3.A4re_Suche|binärer Suche]] wesentlich einfacher.)&lt;br /&gt;
# Der Schlüssel, der die Anordnung bestimmt, kennt eine [http://de.wikipedia.org/wiki/Ordnungsrelation Ordnung] (&amp;lt;-Relation oder &amp;gt;-Relation).&lt;br /&gt;
&lt;br /&gt;
Zunächst definieren wir eine Knotenklasse für den Suchbaum:&lt;br /&gt;
 &lt;br /&gt;
 class Node:&lt;br /&gt;
     def __init__(self, key):&lt;br /&gt;
         self.key = key&lt;br /&gt;
         self.left = self.right = None&lt;br /&gt;
&lt;br /&gt;
=== Suche in einem Binärbaum ===&lt;br /&gt;
&lt;br /&gt;
Wir nehmen nun an, dass der Baum durch eine Referenz auf den Wurzelknoten &amp;lt;tt&amp;gt;root&amp;lt;/tt&amp;gt; gegeben ist. Dann kann man folgendermassen suchen:&lt;br /&gt;
  &lt;br /&gt;
 root = ...    # Wurzel des Suchbaums&lt;br /&gt;
 nodeFound = treeSearch(root, key)   # None, falls nichts gefunden&lt;br /&gt;
 &lt;br /&gt;
Hier verwenden wir die Konvention, dass der passende Knoten zurückgegeben wird, falls &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; gefunden wurde, oder &amp;lt;tt&amp;gt;None&amp;lt;/tt&amp;gt; andernfalls. Die Suchfunktion wird rekursiv implementiert:&lt;br /&gt;
&lt;br /&gt;
 def treeSearch(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return None&lt;br /&gt;
     elif node.key == key: # gefunden&lt;br /&gt;
         return node       # =&amp;gt; Knoten zurückgeben&lt;br /&gt;
     elif key &amp;lt; node.key:  # gesuchter Schlüssel ist kleiner&lt;br /&gt;
         return treeSearch(node.left, key)  # =&amp;gt; im linken Unterbaum weitersuchen&lt;br /&gt;
     else:                 # andernfalls &lt;br /&gt;
         return treeSearch(node.right, key) # =&amp;gt; im rechten Unterbaum weitersuchen&lt;br /&gt;
&lt;br /&gt;
=== Einfügen in einen Binärbaum ===&lt;br /&gt;
&lt;br /&gt;
Bevor wir den Einfügealgorithmus implementieren, müssen wir festlegen, was passieren soll, wenn der einzufügende Schlüssel schon vorhanden ist. Mehrere Möglichkeiten bieten sich an:&lt;br /&gt;
* Fehler signalisieren (exception auslösen)&lt;br /&gt;
* nichts einfügen&lt;br /&gt;
* nichts einfügen, aber einen boolean zurückgeben (false wenn nichts eingefügt wurde, true wenn etwas einfügt wurde)&lt;br /&gt;
* nochmals einfügen (z.B. kann man die Klasse Node oben durch einen Zähler erweitern, der angibt, wie oft der betreffende Schlüssel bereits eingefügt wurde)&lt;br /&gt;
&lt;br /&gt;
Die ersten 3 Punkte realisieren eine Mengensemantik, der letzte eine Multimenge. Wir entscheiden uns hier für Möglichkeit 2 (nichts einfügen). Das Prinzip des Einfügens besteht darin, im Baum dorthin abzusteigen, wo der Schlüssel sich befinden müsste (wie bei &amp;lt;tt&amp;gt;treeSearch&amp;lt;/tt&amp;gt;), und dann an der betreffenden Stelle einen neuen Blattknoten zu erzeugen. Die Funktion gibt ein Knotenobjekt zurück, damit die Verkettungen im Elternknoten entsprechend angepasst werden können:&lt;br /&gt;
&lt;br /&gt;
 def treeInsert(node, key):&lt;br /&gt;
     if node is None:      # richtiger Platz gefunden&lt;br /&gt;
         return Node(key)  # =&amp;gt; neuen Knoten einfügen&lt;br /&gt;
     if node.key == key:   # schon vorhanden&lt;br /&gt;
         return node       # =&amp;gt; nichts tun&lt;br /&gt;
     elif key &amp;lt; node.key:     &lt;br /&gt;
         node.left = treeInsert(node.left, key) # im linken Teilbaum einfügen&lt;br /&gt;
     else:&lt;br /&gt;
         node.right = treeInsert(node.right, key) # im rechten Teilbaum einfügen&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
Ein Binärbaum wird aufgebaut, indem &amp;lt;tt&amp;gt;treeInsert&amp;lt;/tt&amp;gt; für jeden Schlüssel aufgerufen wird. Wir verwenden hier ganze Zahlen als Schlüssel. Am Anfang ist der Baum leer:&lt;br /&gt;
&lt;br /&gt;
 root = None&lt;br /&gt;
 root = treeInsert(root, 4)&lt;br /&gt;
 root = treeInsert(root, 2)&lt;br /&gt;
 root = treeInsert(root, 3)&lt;br /&gt;
 root = treeInsert(root, 6)&lt;br /&gt;
&lt;br /&gt;
=== Entfernen aus einem Binärbaum ===&lt;br /&gt;
Wir legen wiederum zuerst fest, was im Fehlerfall passieren soll, d.h. wenn der Schlüssel nicht vorhanden ist:&lt;br /&gt;
* Auslösen einer Exception (KeyError)&lt;br /&gt;
* nichts löschen&lt;br /&gt;
* nichts löschen, aber ein boolean zurückgeben, das dies signalisiert.&lt;br /&gt;
&lt;br /&gt;
Wir entscheiden uns wieder für Möglichkeit 2. Beim Entfernen eines Knotens unterscheiden wir nun 3 Fälle:&lt;br /&gt;
# node, welcher &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; enthält, ist ein Blatt =&amp;gt; kann einfach gelöscht werden&lt;br /&gt;
# node hat &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; linken Unterbaum oder &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; rechten Unterbaum =&amp;gt; durch Unterbaum ersetzen&lt;br /&gt;
# node hat beide Unterbäume:&lt;br /&gt;
#* Suche Vorgänger: &amp;lt;math&amp;gt;\max_{k &amp;lt; key} (k \in keys)&amp;lt;/math&amp;gt; =&amp;gt; ersetze node durch seinen Vorgänger und entferne Vorgänger. (Dies führt zu einem effizienten Algorithmus, weil der Vorgänger immer zu Fall 1 oder Fall 2 gehört. Wenn er nämlich einen rechten Unterbaum hätte, könnte er nicht der Vorgänger sein.)&lt;br /&gt;
&lt;br /&gt;
Die Funktion, die den Vorgänger sucht, muss den größten Knoten im lnken Unterbaum suchen. Da diese Funktion nur in Fall 3 aufgerufen wird, gibt es den linken Unterbaum immer.&lt;br /&gt;
 def treePredecessor(node):&lt;br /&gt;
     node = node.left&lt;br /&gt;
     while node.right is not None:&lt;br /&gt;
         node = node.right&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
Die oben angegebenen Fälle werden durch folgende Funktion realisiert:&lt;br /&gt;
&lt;br /&gt;
 def treeRemove(node, key):&lt;br /&gt;
     if node is None:   # key nicht vorhanden&lt;br /&gt;
         return node    # =&amp;gt; nichts tun&lt;br /&gt;
     if key &amp;lt; node.key: &lt;br /&gt;
         node.left = treeRemove(node.left, key)&lt;br /&gt;
     elif key &amp;gt; node.key:&lt;br /&gt;
         node.right = treeRemove(node.right, key)&lt;br /&gt;
     else:              # key gefunden&lt;br /&gt;
         if node.left is None and node.right is None:     # Fall 1&lt;br /&gt;
             node = None            &lt;br /&gt;
         elif node.left is None:     # Fall 2&lt;br /&gt;
             node = node.right       # +&lt;br /&gt;
         elif node.right is None:    # Fall 2&lt;br /&gt;
             node = node.left&lt;br /&gt;
         else:                       # Fall 3&lt;br /&gt;
             pred = treePredecessor(node)&lt;br /&gt;
             node.key = pred.key&lt;br /&gt;
             node.left = treeRemove(node.left, pred.key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
=== Komplexitätsanalyse ===&lt;br /&gt;
&lt;br /&gt;
Um die Komplexität der Operationen auf einem Binärbaum zu bestimmen, müssen wir zunächst einige weitere Begriffe einführen:&lt;br /&gt;
;Pfad: Ein Pfad zwischen zwei Knoten node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; ist eine Folge von Knoten node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt;,...,node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt;, so dass:&lt;br /&gt;
:* node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;&lt;br /&gt;
:* node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;&lt;br /&gt;
:* node&amp;lt;sub&amp;gt;ki&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;ki+1&amp;lt;/sub&amp;gt; haben eine gemeinsame Kante.&lt;br /&gt;
[[Image:Baum_Pfad.png]]&lt;br /&gt;
Ein Baum ist definiert als ein Graph, in dem es zwischen beliebigen Knoten stets genau einen Pfad gibt.&lt;br /&gt;
&lt;br /&gt;
;Länge eines Pfades: Anzahl der Kanten im Pfad (= Anzahl der Knoten - 1)&lt;br /&gt;
;Tiefe eines Knotens: Pfadlänge vom Knoten zur Wurzel des Baumes (die Wurzel hat also die Tiefe 0)&lt;br /&gt;
;Tiefe des Baumes: maximale Tiefe eines Knotens&lt;br /&gt;
&lt;br /&gt;
Allen Baumoperationen ist gemeinsam, dass sie entlang genau eines Pfades im Baum absteigen (welcher Pfad dies ist ergibt sich aus der Ordnung der Schlüssel). Der Abstieg endet, wenn entweder der gesuchte Schlüssel gefunden wird, oder wenn erkannt wird, dass der Schlüssel nicht vorhanden ist (wenn das Kind, wo der Schlüssel sein müsste, den Wert &amp;lt;tt&amp;gt;None&amp;lt;/tt&amp;gt; hat). Während des Abstiegs werden in jedem Knoten nur Anweisungen ausgeführt, die konstante Zeit benötigen (1 Vergleich, wenn die Suche in dem Knoten erfolglos beendet wird, 2 Vergleiche, wenn der Schlüssel gefunden wird, und 3 Vergleiche, wenn im rechten oder linken Teilbaun weiter abgestiegen werden muss). Daraus folgt, dass die Suche im ungünstigsten Fall die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(T)&amp;lt;/math&amp;gt; hat, wobei T die Tiefe des Baumes (= längster Pfad, der durchlaufen werden kann) ist.&lt;br /&gt;
&lt;br /&gt;
==== Ungünstigster Fall für die Baumoperationen ====&lt;br /&gt;
&lt;br /&gt;
Um den ungünstigsten Fall für die Baumoperationen zu finden, müssen wir offensichtlich herausfinden, wie groß die Tiefe maximal werden kann. Es ist leicht zu erkennen, dass die Tiefe maximiert wird, wenn man sortierte Daten in den Baum einfügt:&lt;br /&gt;
* Fügt man [1,2,3,4,5] in dieser Reihenfolge ein, muss man bei &amp;lt;tt&amp;gt;treeInsert&amp;lt;/tt&amp;gt; stets in den rechten Teilbaum absteigen (weil der nächste Schlüssel immer größer als der größte bisherige Schlüssel ist) und dort ein rechtes Kind einfügen. Es ergibt sich folgender Baum:&amp;lt;br /&amp;gt; [[Image:Balance.png]]&lt;br /&gt;
: Dieser Baum hat die Tiefe 4. Die Funktion &amp;lt;tt&amp;gt;treeSerach&amp;lt;/tt&amp;gt; verhält sich dann wie sequentielle Suche, man hat also durch die Verwendung des Suchbaums nichts gewonnen. &lt;br /&gt;
Allgemein gilt: Alle Operationen eine binären Suchbaums haben im ungünstigsten Fall die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt;, wo N die Anzahl der Elemente im Baum bezeichnet. Eine offensichtliche Lösung der Problems besteht darin, die Elemente nicht in einer so ungünstigen Reihenfolge einzufügen (siehe Übungsaufgabe 5.1.c). Allerdings ist dies nicht immer möglich. Abhilfe schaffen dann selbst-balancierende Bäume.&lt;br /&gt;
&lt;br /&gt;
==Selbst-balancierende Suchbäume==&lt;br /&gt;
&lt;br /&gt;
=== Balance eines Suchbaumes ===&lt;br /&gt;
&lt;br /&gt;
Um die Komplexität der Suchbaum-Operationen zu minimieren, müssen wir die Höhe des Baumes minimieren. Wir wollen also die Länge des längsten Pfades verkürzen, ohne dass ein anderer Pfad dadurch unnötig lang wird. Mit anderen Worten wollen wir erreichen, dass alle Pfade von der Wurzel zu den Blättern ungefährt die gleiche Länge haben. Diese Idee kann man formal durch den Begriff der ''Balance'' eines Suchbaums fassen. Um die Balance zu definieren, betrachten wir &amp;lt;tt&amp;gt;None&amp;lt;/tt&amp;gt; als zusätzlichen Knoten, als sogenannten '''Sentinel''' (engl. für ''Wächter''). Der sentinel-Knoten wird als rechter oder linker Nachfolger verlinkt, wenn der entsprechende Nachfolger nicht durch einen echten Knoten belegt ist:&lt;br /&gt;
&lt;br /&gt;
[[Image:sentinel.png|400px|right]]&lt;br /&gt;
&lt;br /&gt;
Wir definieren nun:&lt;br /&gt;
;RS-Pfade: Pfad von ''root'' &amp;amp;rarr; ''sentinel''. In jedem Binärbaum gibt es mehrere RS-Pfade.&lt;br /&gt;
;Balance eines Baumes: Differenz zwischen der Länge des längsten und kürzesten RS-Pfads:&lt;br /&gt;
:::&amp;lt;math&amp;gt; B = \max_{P\in\{RS\}} |P| - \min_{P\in\{RS\}} |P|&amp;lt;/math&amp;gt;&lt;br /&gt;
:wobei &amp;lt;math&amp;gt;\{RS\}&amp;lt;/math&amp;gt; die Menge aller RS-Pfade bezeichnet, und |P| die Länge des Pfades P.&lt;br /&gt;
;vollständiger Baum:  Balance &amp;lt;math&amp;gt;B=0&amp;lt;/math&amp;gt;&lt;br /&gt;
:Daraus folgt, dass alle Knoten (außer den Blättern) 2 Kinder haben müssen.&lt;br /&gt;
;perfekt balancierter Baum:  Balance  &amp;lt;math&amp;gt;B \le 1&amp;lt;/math&amp;gt;&lt;br /&gt;
::alternative Definition für perfekt balancierte Bäume: Für jeden Knoten gilt, dass der rechte und linke Unterbaum ebenfalls perfekt balancierte Bäume sind und ihre Höhe sich höchstens um '''1''' unterscheidet. Leere Unterbäume sind per Definition perfekt balanciert und haben die Höhe Null.&lt;br /&gt;
&lt;br /&gt;
====Größe eines Baumes in Abhängigkeit von Balance und Tiefe====&lt;br /&gt;
[[Image:Baum_voll.png|400px|right]]&lt;br /&gt;
;vollständiger Baum:&lt;br /&gt;
Aus der Abbildung erkennt man, dass Ebene k eines vollständigen Baumes stets 2&amp;lt;sup&amp;gt;k&amp;lt;/sup&amp;gt; Knoten enthält (der grüne Knoten gehört nicht zum vollständigen Baum). Hat der Baum die Tiefe d, dann enthält er &lt;br /&gt;
&lt;br /&gt;
::N = 2&amp;lt;sup&amp;gt;0&amp;lt;/sup&amp;gt; + 2&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;.....+ 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; = 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1 &lt;br /&gt;
&lt;br /&gt;
Knoten (und damit ebensoviele Datenelemente).&lt;br /&gt;
&lt;br /&gt;
;perfekt balancierter Baum:&lt;br /&gt;
Für eine gegebene Tiefe d kann kein Baum mehr Elemente enthalten als der entsprechende vollständige Baum. Also gilt für jeden perfekt balancierten Baum der Größe N:&lt;br /&gt;
:::&amp;lt;math&amp;gt; N \le 2^{d+1} - 1&amp;lt;/math&amp;gt;&lt;br /&gt;
Der kleinste perfekt balancierte Baum der Tiefe d ist ein vollständiger Baum der Tiefe d-1 (mit &amp;lt;math&amp;gt;2^{(d-1)+1} - 1&amp;lt;/math&amp;gt; Knoten), wo an einem einzigen Knoten noch ein weiteres Datenelement angehängt wurde (grüner Knoten in der Abbildung). Dieser Baum enthält&lt;br /&gt;
:::&amp;lt;math&amp;gt;N = \left(2^{(d-1)+1} - 1\right) + 1 = 2^d&amp;lt;/math&amp;gt;&lt;br /&gt;
Datenelemente. Folglich gilt für perfekt balancierte Bäume die Ungleichung&lt;br /&gt;
:::&amp;lt;math&amp;gt;2^d \le N \le 2^{d+1} - 1&amp;lt;/math&amp;gt;&lt;br /&gt;
und demzufolge auch&lt;br /&gt;
:::&amp;lt;math&amp;gt;\log_2(2^d) \le \log_2(N) \le \log_2(2^{d+1} - 1) &amp;lt; \log_2(2^{d+1})&amp;lt;/math&amp;gt;&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
:::&amp;lt;math&amp;gt;d \le \log_2(N) &amp;lt; d+1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Da die Baumoperationen im ungünstigsten Fall die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(d)&amp;lt;/math&amp;gt; haben, gilt für perfekt balancierte Bäume, dass alle Operationen im schlechtesten Fall die Komplexität&lt;br /&gt;
:::&amp;lt;math&amp;gt;\mathcal{O}(\log(N))&amp;lt;/math&amp;gt;&lt;br /&gt;
haben, das ist ''logarithmische Komplexität''. Ein perfekt balancierter Baum wird z.B. durch die Datenstruktur des [http://en.wikipedia.org/wiki/AVL_tree AVL-Baums] realisiert. Die Implementation eines AVL-Baums ist jedoch kompliziert, und es zeigt sich, dass die Eingenschaft der perfekten Balance gar nicht notwendig ist, um logarithmische Komplexität zu garantieren. Wir definieren:&lt;br /&gt;
;balancierter Baum: Für die Tiefe d(N) eines balancierten Baumes mit N Knoten gilt&lt;br /&gt;
:::&amp;lt;math&amp;gt;\forall  N:d(N)\le c \cdot d_{PB}(N)&amp;lt;/math&amp;gt; mit &amp;lt;math&amp;gt;1 \le c &amp;lt; \infty&amp;lt;/math&amp;gt;&lt;br /&gt;
:wobei d&amp;lt;sub&amp;gt;PB&amp;lt;/sub&amp;gt;(N) die Tiefe eines perfekt balancierten Baumes mit N Knoten ist. Für die Komplexität der Operationen in einem balancierten Baum gilt dann:&lt;br /&gt;
:::&amp;lt;math&amp;gt;f(N) \le  c\cdot f_{PB}(N) = c\, \mathcal{O}(\log(N)) = \mathcal{O}(\log(N))&amp;lt;/math&amp;gt;&lt;br /&gt;
d.h. die Komplexität ändert sich nicht. Balancierte Bäume sind fast genauso schnell wie perfekt balancierte Bäume (bis auf den Faktor c), aber ihr Aufbau ist algorithmisch einfacher.&lt;br /&gt;
&lt;br /&gt;
===Idee selbst-balancierende Bäume===&lt;br /&gt;
&lt;br /&gt;
Die grundlegende Idee der selbst-balancierenden Bäume besteht darin, nach jeder Einfügung die Balance des Baumes zu optimieren. Dies geschieht am zweckmäßigsten im aufsteigenden Zweig der Rekursion, also nach der Rückkehr von den rekursiven Aufrufen der Funktion &amp;lt;tt&amp;gt;treeInsert&amp;lt;/tt&amp;gt;. Dies entspricht folgendem Pseudo-Code:&lt;br /&gt;
&lt;br /&gt;
  def insertTree(node,key):&lt;br /&gt;
      if node is None: &lt;br /&gt;
          return Node(key)       &lt;br /&gt;
      if node.key == key:&lt;br /&gt;
          return node&lt;br /&gt;
      if key &amp;lt; node.key:&lt;br /&gt;
          node.left  = insertTree(node.left, key)&lt;br /&gt;
      else:&lt;br /&gt;
          node.right = insertTree(node.right, key)    &lt;br /&gt;
      &amp;lt;font color=&amp;quot;red&amp;quot;&amp;gt;optimiere die Balance hier&amp;lt;/font&amp;gt;&lt;br /&gt;
      return node&lt;br /&gt;
&lt;br /&gt;
Dabei muss man beachten, dass bei den Optimierungen die Suchbaumbedingung (Definition siehe oben) erhalten bleibt. Dies ist garantiert, wenn alle Umstrukturierungen durch die elementare Operation der ''Rotation'' implementiert werden. Eine ''Rechtsrotation'' ersetzt die Wurzel &amp;lt;tt&amp;gt;n&amp;lt;/tt&amp;gt; eines Teilbaumes durch sein linkes Kind, und fügt die alte Wurzel als rechtes Kind der neuen Wurzel ein. Die ''Linksrotation'' ist die Inverse dieser Operation. Die Abbildung verdeutlicht die Umstrukturierungen:&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum_Rotation.png]]&lt;br /&gt;
&lt;br /&gt;
Die Rotationen werden wie folgt implementiert:&lt;br /&gt;
&lt;br /&gt;
 def rotateRight(node):&lt;br /&gt;
     newRoot = node.left&lt;br /&gt;
     node.left = newRoot.right&lt;br /&gt;
     newRoot.right = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
 def rotateLeft(node):&lt;br /&gt;
     newRoot = node.right&lt;br /&gt;
     node.right = newRoot.left&lt;br /&gt;
     newRoot.left = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
Man erkennt leicht, dass die Suchbaumbedingung erhalten bleibt. Wir erläutern dies für die Rechtsrotation, bei der Linksrotation gilt die Erklärung entsprechend. Knoten ''n'' hat einen größeren Schlüssel als Knoten ''L'', denn ''L'' ist vor der Rechtsrotation das linke Kind von ''n''. Nach der Rotation ist ''n'' deshalb korrekterweise das rechte Kind von ''L''. Weiter gilt für den Teilbaum mit der Wurzel ''LR'', dass er größer als ''L'' ist (denn er ist das rechte Kind von ''L''), aber kleiner als ''n'' (denn er liegt im linken Teilbaum von ''n''). Nach der Rechtsrotation ist diese Bedingung immer noch erfüllt, denn ''LR'' ist jetzt linker Teilbaum von ''n'', welches wiederum rechter Teilbaum von ''L'' geworden ist. Alle anderen Teilbäume sind von der Rotation nicht betroffen.&lt;br /&gt;
&lt;br /&gt;
Verschiedene Arten von selbst-balancierenden Bäumen unterscheiden sich im Wesentlichen dadurch, wann welche Rotation ausgeführt wird. Wichtige Beispiele sind&lt;br /&gt;
* [http://en.wikipedia.org/wiki/AVL_tree AVL-Bäume] (älteste Variante)&lt;br /&gt;
* [http://en.wikipedia.org/wiki/Red_black_tree Rot-Schwarz-Bäume] (verbreitetste Variante)&lt;br /&gt;
* [http://en.wikipedia.org/wiki/Treap Treaps] (flexibelste Variante, siehe Übung 6.1)&lt;br /&gt;
* [http://en.wikipedia.org/wiki/Splay_tree Splay trees]&lt;br /&gt;
* [http://en.wikipedia.org/wiki/AA_tree Andersson-Bäume] (einfachste Variante, siehe unten)&lt;br /&gt;
&lt;br /&gt;
Daneben wird gern die [http://en.wikipedia.org/wiki/Skip_list Skip List] verwendet, die aber kein Binärbaum ist, sondern auf einem anderen Prinzip beruht.&lt;br /&gt;
&lt;br /&gt;
===Andersson-Bäume===&lt;br /&gt;
&lt;br /&gt;
Jeder selbst-balancierende Baum benötigt Zusatzinformationen, die die augenblickliche Balance beschreiben, so dass diese gegebenenfalls optimiert werden kann. Der Andersson-Baum fügt zu diesem Zweck in jedem Knoten ein neues Feld ''level'' ein, welches mit 1 initialisiert wird:&lt;br /&gt;
&lt;br /&gt;
  class AnderssonNode:&lt;br /&gt;
    def__init__(self, key):&lt;br /&gt;
        self.key = key&lt;br /&gt;
        self.left = self.right = None&lt;br /&gt;
        self.level = 1&lt;br /&gt;
&lt;br /&gt;
Grob gesprochen kodiert das ''level''-Feld den Abstand des Knotens vom Sentinel. Genauer gelten folgende&lt;br /&gt;
&lt;br /&gt;
====Regeln====&lt;br /&gt;
&lt;br /&gt;
* Es gibt vertikale Kanten (parent.level == child.level + 1 ) und horizontale Kanten (parent.level == child.level). &lt;br /&gt;
* Die ''reduzierte Länge'' eines Pfades zwischen zwei Knoten wird berechnet, indem nur die vertikalen Kanten im Pfad gezählt werden.&lt;br /&gt;
* Das Sentinel hat ''level = 0''. Alle Kanten zum Sentinel sind vertikal.&lt;br /&gt;
* Die ''reduzierte Höhe'' eines Knotens entspricht der reduzierten Länge des Pfades von diesem Knoten zum Sentinel. Das ''level''-Feld jedes Knotens speichert die reduzierte Höhe dieses Knotens. Folglich gilt für alle Knoten, die direkt mit dem Sentinel verbunden sind, ''level = 1''. Insbesondere gilt dies auch für neu eingefügte Knoten (siehe obige Initialisierung).&lt;br /&gt;
&lt;br /&gt;
Die nächsten zwei Regeln sichern die Balance:&lt;br /&gt;
* Alle RS-Pfade haben die gleiche reduzierte Länge. Dies ist äquivalent zu der Bedingung, dass die Wurzel des Andersson-Baumes über alle möglichen RS-Pfade auf dem gleichen Level erreicht wird.&lt;br /&gt;
* Kein Pfad hat 2 aufeinander folgende horizontale Kanten. &lt;br /&gt;
&lt;br /&gt;
Die letzte Regel führt zu starken algorithmischen Vereinfachungen gegenüber den konzeptionell sehr ähnlichen Rot-Schwarz-Bäumen:&lt;br /&gt;
* Nur Kanten zum rechten Kind dürfen horizontal sein.&lt;br /&gt;
&lt;br /&gt;
Das folgende Bild zeigt einen Andersson-Baum, bei dem allerdings nicht alle Verbindungen zum Sentinel eingezeichnet sind:&lt;br /&gt;
&lt;br /&gt;
[[Image:Abild.png]]&lt;br /&gt;
&lt;br /&gt;
Es gilt folgender&lt;br /&gt;
;Satz: Jeder Andersson-Baum ist balanciert. Beweis:&lt;br /&gt;
:1. Sei ''h&amp;lt;sub&amp;gt;r&amp;lt;/sub&amp;gt;'' die reduzierte Höhe des Andersson-Baumes. Die Eigenschaft, dass alle RS-Pfade die reduzierte Länge ''h&amp;lt;sub&amp;gt;r&amp;lt;/sub&amp;gt;'' (also die ''gleiche'' reduzierte Länge) haben, hat eine wichtige Folge: Hat der Andersson-Baum ''keine'' horizontalen Kanten, so muss er ein vollständiger Baum der Tiefe ''d&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt; = h&amp;lt;sub&amp;gt;r&amp;lt;/sub&amp;gt; - 1'' sein, denn nur ein vollständiger Baum hat die Eigenschaft, dass alle RS-Pfade die gleiche Länge besitzen. Gibt es hingegen horizontale Kanten, muss der Andersson-Baum ''mehr'' Elemente enthalten als der vollständige Baum der Tiefe ''d&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt;''. Folglich gilt für die Anzahl der Knoten eines Andersson-Baumes:&lt;br /&gt;
:::&amp;lt;math&amp;gt;N \ge 2^{d_v+1} - 1 = 2^{h_r} - 1&amp;lt;/math&amp;gt;&lt;br /&gt;
:2. Da niemals zwei aufeinenderfolgende Kanten horizontal sein dürfen, ist in jedem RS-Pfad höchstens die Hälfte aller Kanten horizontal. Daher gilt für die Tiefe ''d'' eines Andersson-Baumes&lt;br /&gt;
:::&amp;lt;math&amp;gt;d \le 2 h_r&amp;lt;/math&amp;gt;&lt;br /&gt;
:3. Fasst man 1. und 2. zusammen, erhält man:&lt;br /&gt;
:::&amp;lt;math&amp;gt;N \ge 2^{h_r} - 1 \ge 2^{d/2} - 1&amp;lt;/math&amp;gt;&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
:::&amp;lt;math&amp;gt;N + 1 \ge 2^{d/2}&amp;lt;/math&amp;gt;&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
:::&amp;lt;math&amp;gt;\log_2(N + 1) \ge d/2&amp;lt;/math&amp;gt;&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
:::&amp;lt;math&amp;gt;d \le 2 \log_2(N + 1)&amp;lt;/math&amp;gt;.&lt;br /&gt;
::Da die Komplexität der Baumoperationen &amp;lt;math&amp;gt;f(N) = \mathcal{O}(d)&amp;lt;/math&amp;gt; ist, gilt für den Andersson-Baum:&lt;br /&gt;
:::&amp;lt;math&amp;gt;f(N) = \mathcal{O}(2 \log_2(N + 1)) = \mathcal{O}(\log(N))&amp;lt;/math&amp;gt; &lt;br /&gt;
::q.e.d.&lt;br /&gt;
&lt;br /&gt;
====Wie erreicht man die Balance====&lt;br /&gt;
&lt;br /&gt;
Der Baum ist nicht mehr balanciert, wenn obige Regeln verletzt sind. Dies kann durch Einfügen eines neuen Knotens oder durch Löschen eines Knotens passieren. Nach jeder Einfügung haben sowohl der neue Knoten als auch sein Vater das Level 1 (denn der Vater war vorher direkt mit dem Sentinel verbunden). Kanten zu neu eingefügten Knoten sind deshalb immer horizontal. Dies kann die Regeln verletzen, indem entweder&lt;br /&gt;
* eine horizontale Kante zum linken Kind enstanden ist (falls der neue Knoten ein linkes Kind ist), oder&lt;br /&gt;
* zwei aufeinander folgende horizontale Kanten zu rechten Kindern entstanden sind (falls der neue Knoten ein rechtes Kind ist, und sein Vater bereits ein horizontales rechtes Kind war).&lt;br /&gt;
Diese Fehler können durch Rotation leicht behoben werden:&lt;br /&gt;
* Linke horizontale Kanten werden durch Rechtsrotation in rechte horizontale Kanten verwandelt.&lt;br /&gt;
* Bei zwei aufeinander folgenden rechten horizontalen Kanten wird der mittlere Knoten um eine Ebene angehoben.&lt;br /&gt;
Dabei ist zu beachten, dass die erste Reparatur einen neuen Fehler erzeugen kann: Es können zwei aufeinanderfolgende rechte horizontale Kanten enstehen. Daher muss die zweite Operation stets nach der ersten ausgeführt werden. Das Anheben des Levels in der zweiten Operation kann wiederum dazu führen, dass auf der nächsthöheren Ebene verbotene horizontale Kanten entstehen. Deshalb müssen die Reparaturoperationen auf der nächsten Ebene rekursiv wiederholt werden. Dies führt uns zu folgender Implementation des Insert-Algorithmus&lt;br /&gt;
&lt;br /&gt;
  def anderssonTreeInsert(node,key):&lt;br /&gt;
      if node is None: &lt;br /&gt;
          return AnderssonNode(key)       &lt;br /&gt;
      if node.key == key:&lt;br /&gt;
          return node&lt;br /&gt;
      if key &amp;lt; node.key:&lt;br /&gt;
          node.left  = anderssonTreeInsert(node.left, key)&lt;br /&gt;
      else:&lt;br /&gt;
          node.right = anderssonTreeInsert(node.right, key)    &lt;br /&gt;
      &amp;lt;font color=&amp;quot;red&amp;quot;&amp;gt;if node.left is not None and node.level == node.left.level: # linke horizontale Kante&lt;br /&gt;
            node = rotateRight(node)  # wird zu rechter horizontaler Kante gemacht&lt;br /&gt;
      if node.right is not None and node.right.right is not None and node.level==node.right.right.level:  # aufeinanderfolgende horizontale Kanten&lt;br /&gt;
            node = rotateLeft(node)   # mache den mittleren Knoten zur Wurzel des Teilbaums&lt;br /&gt;
            node.level += 1           # und hebe die Wurzel um ein level an&amp;lt;/font&amp;gt;    &lt;br /&gt;
      return node&lt;br /&gt;
&lt;br /&gt;
Da die Reparaturoperationen auf dem Rückweg von der Rekursion ausgeführt werden, ist gewährleistet, dass sie auf der nächsten Ebene des Baumes ebenfalls ausgeführt werden, falls nötig. Die folgende Skizze verdeutlicht die Anwendung der Reparaturen, wenn Knoten ''c'' über eine linke horizontale Kante an Knoten ''b'' angefügt wurde. Im oberen Beispiel genügt die erste Operation zur Reparatur, beim unteren Beispiel muss hingegen auch noch die zweite Operation angewendet werden.&lt;br /&gt;
&lt;br /&gt;
[[Image:rotate.jpg|text-top]]&lt;br /&gt;
&lt;br /&gt;
Die folgende Illustration verdeutlicht das Verhalten des Andersson-Baumes, wenn die Schlüssel in der Folge [6,5,4,3,2,1] eingefügt werden. Beim einfachen Binärbaum sind solche vorsortierten Daten sehr ungünstig und führen zu entarteten Bäumen mit linearer Zugriffzeit (links). Die Umstrukturierungen beim Andersson-Baum stellen hingegen sicher, dass die Balance immer gewahrt bleibt (rechts). &amp;lt;font color=&amp;quot;red&amp;quot;&amp;gt;Diese Illustration sollte unbedingt verbessert werden. Die entscheidenden Punkte sind sehr schwer erkennbar, und es gibt auch Fehler.&amp;lt;/font&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
   6,1          5,1&lt;br /&gt;
    /     -&amp;gt;      \&lt;br /&gt;
 5,1              6,1    &lt;br /&gt;
    &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     5,1         4,1                5,2&lt;br /&gt;
    /   \     -&amp;gt;    \        -&amp;gt;    /   \&lt;br /&gt;
 4,1     6,1        5,1        4,1     6,1&lt;br /&gt;
                       \&lt;br /&gt;
                        6,1   &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
         5,2               5,2&lt;br /&gt;
        /   \    -&amp;gt;       /   \&lt;br /&gt;
     4,1     6,1       3,1     6,1&lt;br /&gt;
     /                    \&lt;br /&gt;
  3,1                      4,1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
         5,2              5,2                 5,2&lt;br /&gt;
        /   \            /   \               /   \&lt;br /&gt;
     3,1     6,1   -&amp;gt;  2,1     6,1  -&amp;gt;     3,2   6,1&lt;br /&gt;
    /   \                \                 /       \&lt;br /&gt;
 2,1     4,1            3,1              2,1       4,1&lt;br /&gt;
                           \&lt;br /&gt;
                            4,1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
         5,2                 3,2&lt;br /&gt;
        /   \               /   \&lt;br /&gt;
     3,2     6,1     -&amp;gt;  2,1     5,2&lt;br /&gt;
    /   \                       /   \&lt;br /&gt;
 2,1     4,1                  4,1    6,1&lt;br /&gt;
 &lt;br /&gt;
Die Löschoperation &amp;lt;tt&amp;gt;anderssonTreeRemove&amp;lt;/tt&amp;gt; benötigt in jedem Knoten bis zu 5 Rotationen. Wegen der Einzelheiten verweisen wir auf Anderssons [http://user.it.uu.se/~arnea/abs/simp.html Originalartikel].&lt;br /&gt;
&lt;br /&gt;
==Beziehungen zwischen dem Suchproblem und dem Sortierproblem==&lt;br /&gt;
&lt;br /&gt;
===Sortieren mit Hilfe eines selbst-balancierenden Suchbaums===&lt;br /&gt;
&lt;br /&gt;
Mit Hilfe eines selbst-balancierenden Suchbaums kann ein effizienter Sortieralgorithmus implementiert werden, indem man zunächst die Daten in beliebiger Reihenfolge in einen Baum einfügt, und dann in der richtigen Sortierung wieder ausliest.&lt;br /&gt;
&lt;br /&gt;
   a = ...   # unsortiertes Array&lt;br /&gt;
   t = None  # leerer Andersson-Baum&lt;br /&gt;
   for e in a:&lt;br /&gt;
       t = anderssonTreeInsert(t, e) # Baum erzeugen&lt;br /&gt;
   r = []    # leeres dynamisches Array&lt;br /&gt;
   treeSort(t, r) &lt;br /&gt;
   # r enthält jetzt die Daten aus a in sortierter Reihenfolge&lt;br /&gt;
&lt;br /&gt;
Die Funktion &amp;lt;tt&amp;gt;treeSort&amp;lt;/tt&amp;gt; navigiert im Sinne eines sogenannten ''in-order traversals'' durch den Baum und fügt die Datenelemente in der richtigen Reihenfolge an des Array an:&lt;br /&gt;
&lt;br /&gt;
 def treeSort(node,array):          # dynamisches Array als 2. Argument&lt;br /&gt;
     if node is None:               # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
         return&lt;br /&gt;
     treeSort(node.left, array)     # rekursiv&lt;br /&gt;
     array.append(node.key)         # amortisiert &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
     treeSort(node.right, array)    # rekursiv&lt;br /&gt;
&lt;br /&gt;
;Komplexität:&lt;br /&gt;
&lt;br /&gt;
* Jede Einfügeoperation in den Baum hat logarithmische Komplexität. Der Aufbau eines Baumes aus N Elementen hat daher Komplexität &amp;lt;math&amp;gt;\mathcal{O}(N \log(N))&amp;lt;/math&amp;gt;.&lt;br /&gt;
* &amp;lt;tt&amp;gt;treeSort&amp;lt;/tt&amp;gt; führt in jedem Knoten eine oder zwei Operationen mit Komplexität &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; sowie zwei rekursive Aufrufe aus. Die Auflösung der Rekursion ergibt&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 f(N)=\mathcal{O}(1)+f(N_\mathrm{left})+f(N_\mathrm{right})=\mathcal{O}(1)+\mathcal{O}(1)+f(N_\mathrm{left.left})+f(N_\mathrm{left.right})+\mathcal{O}(1)+f(N_\mathrm{right.left})&lt;br /&gt;
 +f(N_\mathrm{left.right})=N\cdot\mathcal{O}(1)=\mathcal{O}(N)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
* Insgesamt erhalten wir also Komplexität &amp;lt;math&amp;gt;\mathcal{O}(\max(N \log(N), N)) = \mathcal{O}(N \log(N))&amp;lt;/math&amp;gt; wie bei Merge Sort. Allerdings sind der konstante Faktor sowie der Speicherverbrauch größer, so dass diese Sortiermethode in der Praxis kaum angewendet wird.&lt;br /&gt;
&lt;br /&gt;
===Sortieren als Suchproblem===&lt;br /&gt;
&lt;br /&gt;
Stellt man systematisch Fragen, die nur mit True oder False beantwortet werden können, kann dieses Vorgehen auch als Entscheidungsbaum dargestellt werden.&amp;lt;br&amp;gt; [[Image:penka.png|400px]]&lt;br /&gt;
&lt;br /&gt;
Als Beispiel verwenden wir den Algorithmus zum Sortieren von drei Elementen aus der Vorlesung über [[Sortieren]]. Als Eingabe sind drei Zahlen vorgegeben a={1,2,3}, deren Reihenfolge (Permutation) nicht bekannt ist. Wie die Illustration für den linke Hälfte des Entscheidungsbaumes zeigt, können wir die Reihenfolge mit nur 3 Fragen herausbekommen.&lt;br /&gt;
[[Image:trueFalseBeisp.png|700px]]&amp;lt;br&amp;gt; (Die Reihenfolge der Antworten ''True, False, True'' kann allerdings gar nicht auftreten, weil sie widersprüchlich ist ('''&amp;lt;font color=red&amp;gt;bitte aus der Graphik löschen!&amp;lt;/font&amp;gt;''')&amp;lt;br&amp;gt;Die allgemeine Regel lautet: Wenn es N mögliche Lösungen gibt, muss der Baum N Blätter haben. Wie wir oben gezeigt haben, hat ein Baum mit N Blättern mindestens die Tiefe log(N).&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;0&amp;quot; cellspacing=&amp;quot;0&amp;quot; cellpadding=&amp;quot;5&amp;quot; &lt;br /&gt;
|-valign=&amp;quot;center&amp;quot; &lt;br /&gt;
|[[Image:vollbaum.png|left]]&lt;br /&gt;
| vollständiger Baum [http://hci.iwr.uni-heidelberg.de/alda/index.php/Suchen#Balance_eines_Suchbaumes]&amp;lt;br&amp;gt;2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt;  Knoten&amp;lt;br&amp;gt;2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; Blätter&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Im Fall des Sortierens von n Elementen gilt, dass es N = n! mögliche Permutation gibt. Ein Baum mit n! Blättern hat mindestens die Tiefe log(n!). Im obigen Beispiel für n=3 gilt 3! = 1*2*3 = 6 und damit für die Tiefe d&lt;br /&gt;
:::&amp;lt;math&amp;gt;d = \lceil \log_2(6)\rceil \approx \lceil 2.6\rceil = 3&amp;lt;/math&amp;gt;&lt;br /&gt;
Im ungünstigsten Fall braucht man bei dem Frage-Baum drei Schritte. Weil aber &amp;lt;math&amp;gt;\log(6)\approx 2.6 &amp;lt; 3&amp;lt;/math&amp;gt; muss nicht jeder Pfad zu Ende durchlaufen werden, um die Lösung zu bekommen.&lt;br /&gt;
&lt;br /&gt;
Allgemein gilt&lt;br /&gt;
::&amp;lt;math&amp;gt;d \ge \log_2(n!) = \log_2(1\cdot 2\cdot ... \cdot n) = \log_2(1) + \log_2(2) + ... + \log_2(n) = \sum_{k=1}^n \log_2(k) = \frac{1}{\ln(2)}\sum_{k=1}^n \ln(k) = \frac{1}{\ln(2)}\sum_{k=2}^n \ln(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
Die letzte Identität gilt, weil &amp;lt;math&amp;gt;\ln(1) = 0&amp;lt;/math&amp;gt; in der Summe weggelassen werden kann. Eine untere Schranke für die Tiefe kann man explizit bestimmen durch die Methode der&lt;br /&gt;
 &lt;br /&gt;
====Abschätzung von Summen durch Integrale====&lt;br /&gt;
&lt;br /&gt;
Gegeben sei eine monoton wachsende Funktion f(x) (blaue Kurve). Das bestimmte Integral über die Funktion sei&lt;br /&gt;
:::&amp;lt;math&amp;gt;\int_{x_1}^{x_2} f(x)dx&amp;lt;/math&amp;gt;. &lt;br /&gt;
Wenn wir das Funktionsargument x abrunden (schwarze Kurve), entsteht ein Integral, das einen kleineren Wert als das ursprüngliche Integral hat. Runden wir auf (rote Kurve), entsteht ein Integral mit einem größeren Wert:&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;0&amp;quot; cellspacing=&amp;quot;0&amp;quot; cellpadding=&amp;quot;5&amp;quot; &lt;br /&gt;
|-valign=&amp;quot;center&amp;quot; &lt;br /&gt;
|[[Image:integralGraph.png|400px|left]]&lt;br /&gt;
| &amp;lt;math&amp;gt;\int_{x_1}^{x_2} f(\lfloor x \rfloor)dx \le \int_{x_1}^{x_2} f(x)dx \le \int_{x_1}^{x_2} f(\lceil x \rceil)dx&amp;lt;/math&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
In unserem Zusammenhang sind x&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; und x&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; positive ganze Zahlen. Deshalb gilt&lt;br /&gt;
:::&amp;lt;math&amp;gt;f(\lfloor x \rfloor)_{x_1}^{x_1+1}= f(x_1),&amp;lt;/math&amp;gt;&lt;br /&gt;
:::&amp;lt;math&amp;gt;f(\lfloor x \rfloor)_{x_1+1}^{x_1+2}= f(x_1+1)&amp;lt;/math&amp;gt;&lt;br /&gt;
:::&amp;lt;math&amp;gt;...&amp;lt;/math&amp;gt;&lt;br /&gt;
:::&amp;lt;math&amp;gt;f(\lfloor x \rfloor)_{x_2-1}^{x_2}= f(x_2-1)&amp;lt;/math&amp;gt;&lt;br /&gt;
Wir können die obigen Integrale daher folgendermaßen vereinfachen:&lt;br /&gt;
:::&amp;lt;math&amp;gt;\begin{array}{lcl}&lt;br /&gt;
\int_{x_1}^{x_2} f(\lfloor x \rfloor) dx &amp;amp;=&amp;amp; \int_{x_1}^{x_1 + 1} f(\lfloor x \rfloor) dx + ...+ \int_{x_2-1}^{x_2} f(\lfloor x \rfloor) dx \\&lt;br /&gt;
&amp;amp; = &amp;amp; \int_{x_1}^{x_1 + 1} f(x_1) dx + ...+ \int_{x_2-1}^{x_2} f(x_2-1) dx \\&lt;br /&gt;
&amp;amp; = &amp;amp; f(x_1) \int_{x_1}^{x_1 + 1} dx + ...+ f(x_2-1) \int_{x_2-1}^{x_2} dx \\&lt;br /&gt;
&amp;amp; = &amp;amp; f(x_1)  + ...+ f(x_2-1) \\&lt;br /&gt;
&amp;amp; = &amp;amp; \sum_{k=x_1}^{x_2-1} f(k)&lt;br /&gt;
\end{array}&amp;lt;/math&amp;gt;&lt;br /&gt;
für die Fläche unter den schwarzen Rechtecken sowie&lt;br /&gt;
:::&amp;lt;math&amp;gt;\begin{array}{lcl}&lt;br /&gt;
\int_{x_1}^{x_2} f(\lceil x \rceil) dx &amp;amp;=&amp;amp; \int_{x_1}^{x_1 + 1} f(\lceil x \rceil) dx + ...+ \int_{x_2-1}^{x_2} f(\lceil x \rceil) dx \\&lt;br /&gt;
&amp;amp; = &amp;amp; \int_{x_1}^{x_1 + 1} f(x_1+1) dx + ...+ \int_{x_2-1}^{x_2} f(x_2) dx \\&lt;br /&gt;
&amp;amp; = &amp;amp; f(x_1+1) \int_{x_1}^{x_1 + 1} dx + ...+ f(x_2) \int_{x_2-1}^{x_2} dx \\&lt;br /&gt;
&amp;amp; = &amp;amp; f(x_1+1)  + ...+ f(x_2) \\&lt;br /&gt;
&amp;amp; = &amp;amp; \sum_{k=x_1+1}^{x_2} f(k)&lt;br /&gt;
\end{array}&amp;lt;/math&amp;gt;&lt;br /&gt;
für die Fläche unter den roten Rechtecken. Zusammenfassend gilt also&lt;br /&gt;
&amp;lt;math&amp;gt; \sum_{k=x_1}^{x_2-1} f(k) \le \int_{x_1}^{x_2} f(x)dx&amp;lt;/math&amp;gt; und &lt;br /&gt;
&amp;lt;math&amp;gt; \sum_{k=x_1+1}^{x_2} f(k) \ge \int_{x_1}^{x_2} f(x)dx&amp;lt;/math&amp;gt;&lt;br /&gt;
Für unser Problem setzen wir f(k) = ln(k), x&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;+1 = 2, und x&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; = n. Also können wir abschätzen&lt;br /&gt;
:::&amp;lt;math&amp;gt;\sum_{k=x_1+1}^{x_2} f(k) = \frac{1}{\ln(2)}\sum_{k=2}^{n} \ln(k) \ge \frac{1}{\ln(2)}\int_1^n \ln(x) dx&amp;lt;/math&amp;gt;&lt;br /&gt;
Das Integral ist leicht zu lösen, und wir erhalten&lt;br /&gt;
:::&amp;lt;math&amp;gt;\frac{1}{\ln(2)}\sum_{k=2}^{n} \ln(k) \ge \frac{1}{\ln(2)}\left[x\ln(x)-x\right]_{x=1}^{n} = \frac{1}{\ln(2)}(n\ln(n)-n+1)=n\log_2(n) - \frac{n-1}{\ln(2)} \in \Omega(n \log(n))&amp;lt;/math&amp;gt;&lt;br /&gt;
Folglich gilt:&lt;br /&gt;
:::&amp;lt;math&amp;gt;d\ge\log_2(n!) = \frac{1}{\ln(2)}\sum_{k=2}^{n} \ln(k) \in \Omega(n \log(n))&amp;lt;/math&amp;gt;&lt;br /&gt;
Mit anderen Worten: '''Kein Sortieralgorithmus auf Basis paarweise Vergleiche ist asymptotisch schneller als Mergesort, denn die Anzahl der Vergleiche (= Tiefe des Entscheidungsbaumes) ist &amp;lt;math&amp;gt;\Omega(n \log(n))&amp;lt;/math&amp;gt;'''. Falls man einen schnelleren Sortieralgorithmus benötigt, muss man ein anderes algorithmisches Prinzip verwenden, siehe dazu Übungsaufgabe 6.2.&lt;/div&gt;</summary>
		<author><name>Jschleic</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Sortieren&amp;diff=2535</id>
		<title>Sortieren</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Sortieren&amp;diff=2535"/>
				<updated>2008-07-22T12:54:09Z</updated>
		
		<summary type="html">&lt;p&gt;Jschleic: /* Charakterisierung der Effizienz von Algorithmen */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;----&lt;br /&gt;
== Laufzeitmesung in Python ==&lt;br /&gt;
&lt;br /&gt;
Verwendung der '''timeit-Bibliothek''' für die Hausaufgabe. &lt;br /&gt;
&lt;br /&gt;
* Importiere das timeit-Modul: &amp;lt;tt&amp;gt;import timeit&amp;lt;/tt&amp;gt;&lt;br /&gt;
* Teile den Algorithmus in die Initialisierungen und den Teil, dessen Geschwindigkeit gemessen werden soll. Beide Teile werden in jeweils einen (mehrzeiligen) String eingeschlossen:&lt;br /&gt;
&lt;br /&gt;
  +--------+     +----+            setup = &amp;quot;&amp;quot;&amp;quot;            prog = &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
  |  algo  | --&amp;gt; |init|                +----+                 +----+&lt;br /&gt;
  |        |     +----+                |init|                 |prog|&lt;br /&gt;
  |        |                           +----+                 +----+&lt;br /&gt;
  |        |     +----+             &amp;quot;&amp;quot;&amp;quot;                     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
  |        | --&amp;gt; |prog|            &lt;br /&gt;
  +--------+     +----+            &lt;br /&gt;
&lt;br /&gt;
* aus den beiden Strings wird ein Timeit-Objekt erzeugt: &amp;lt;tt&amp;gt;t = timeit.Timer(prog, setup)&amp;lt;/tt&amp;gt;&lt;br /&gt;
* Frage: Wie oft soll die Algorithmik wiederholt werden&lt;br /&gt;
:z.B. N = 1000&lt;br /&gt;
* Zeit in Sekunden für N Durchläufe: &amp;lt;tt&amp;gt;K = t.timeit(N)&amp;lt;/tt&amp;gt;&lt;br /&gt;
:Zeit für 1 Durchlauf: K/N&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
3.Stunde am 16.04.2008&lt;br /&gt;
&lt;br /&gt;
==Sortierverfahren==&lt;br /&gt;
&lt;br /&gt;
=== Motivation ===&lt;br /&gt;
'''Def:''' &lt;br /&gt;
Ein Sortierverfahren ist ein Algorithmus, der dazu dient, eine Liste von Elementen zu sortieren.&lt;br /&gt;
* Literatur, siehe Sortierverfahren; Bubblesort 1956, Quicksort 1962. Librarysort 2004  &lt;br /&gt;
&lt;br /&gt;
'''Anwendungen'''&lt;br /&gt;
* Sortierte Daten sind häufig Vorbedingungen für Suchverfahren (Speziell für effiziente Suchalgorithmen mit Komplexität &amp;lt;math&amp;gt;\mathcal{O}(log(N))&amp;lt;/math&amp;gt;)&lt;br /&gt;
* Darstellung von Daten gemäß menschlicher Wahrnehmung &lt;br /&gt;
* Aus programmiertechnischer Anwendungssicht hat das Sortierproblem allerdings heute an Relevanz verloren da&lt;br /&gt;
** gängige Programmiersprachen heute typunabhängige Algorithmen zur Verfügung stellen. Der Programmierer braucht sich deshalb in den meisten Fällen nicht mehr um die Implementierung von Sortieralgorithmen zu kümmern. In C/C++ sorgen dafür beispielsweise Methoden aus der [http://de.wikipedia.org/wiki/Standard_Template_Library STL].&lt;br /&gt;
** Festplatten / Hauptspeicher heute weniger limitierenden Charakter haben, so dass Standardsortierverfahren meist ausreichen, während komplizierte, speicher-sparende Sortieralgorithmen nur noch selten benötigt werden.&lt;br /&gt;
* Die Kenntnis grundlegender Sortieralgorithmen ist trotzdem immer noch nötig: Einerseits kann man vorgefertigte Bausteine nur dann optimal einsetzen, wenn man weiß, was hinter den Kulissen passiert und andererseits verdeutlicht gerade das Sortierproblem wichtige Prinzipien der Algorithmenentwicklung und -analyse in sehr anschaulicher Form.&lt;br /&gt;
&lt;br /&gt;
=== Vorraussetzungen/ Spielregeln ===&lt;br /&gt;
&lt;br /&gt;
==== Mengentheoretische  Anforderungen====&lt;br /&gt;
Definition Totale Ordnung/ Total gordnete Menge:&lt;br /&gt;
Eine Totale Ordnung / Total geordnete Menge ist eine binäre Relation    &lt;br /&gt;
&amp;lt;math&amp;gt;R \subseteq M \times M&amp;lt;/math&amp;gt; über einer Menge &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt;, die transitiv, antisymmetrisch und total ist.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; sei  dargestellt als infix Notation &amp;lt;math&amp;gt;\le &amp;lt;/math&amp;gt; dann, falls M total geordnet, gilt &lt;br /&gt;
&amp;lt;math&amp;gt; \forall a,b,c \ \epsilon M &amp;lt;/math&amp;gt; &amp;lt;br/&amp;gt;&lt;br /&gt;
(1) &amp;lt;math&amp;gt;a \le b \wedge b \le a \Rightarrow a=b &amp;lt;/math&amp;gt; (antisymmetrisch)&amp;lt;br/&amp;gt;&lt;br /&gt;
(2) &amp;lt;math&amp;gt;a \le b \wedge b \le c \Rightarrow a \le c &amp;lt;/math&amp;gt; (transitiv)&amp;lt;br/&amp;gt;&lt;br /&gt;
(3) &amp;lt;math&amp;gt;a \le b \vee b \le a &amp;lt;/math&amp;gt; (total) &amp;lt;br/&amp;gt;&lt;br /&gt;
Bemerkung: aus (3) folgt &amp;lt;math&amp;gt; a \le a &amp;lt;/math&amp;gt; (reflexiv) &amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
''Hab in der Wiki eine gute Seite dazu gefunden [http://de.wikipedia.org/wiki/Ordnungsrelation'' Ordnungsrelation]&lt;br /&gt;
&lt;br /&gt;
==== Datenspeicherung ====&lt;br /&gt;
&lt;br /&gt;
Die Daten liegen typischerweise in Form von Arrays oder verketteten Listen vor. Ja nach Datenstruktur sind andere Sortieralgorithmen am besten geeignet. &lt;br /&gt;
;Array:&lt;br /&gt;
        +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
        |///|   |   |   |   |   |   |   |///|  &lt;br /&gt;
        +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
       \________________  ____________________/&lt;br /&gt;
                        \/&lt;br /&gt;
                        N&lt;br /&gt;
Datenelemente können über Indexoperation a[i] gelesen, überschrieben und miteinander vertauscht werden. Vorteil: Die Zugriffsreihenfolge auf die Datenelemente ist beliebig. Nachteil: Einfügen oder Löschen von Elementen aus dem Array ist relativ aufwändig.&lt;br /&gt;
&lt;br /&gt;
;Vekettete Liste:  &lt;br /&gt;
        +---+     +---+     +---+&lt;br /&gt;
        |   | --&amp;gt; |   | --&amp;gt; |   | --&amp;gt; Ende &lt;br /&gt;
        +---+     +---+     +---+&lt;br /&gt;
   &lt;br /&gt;
Jeder Knoten der Liste enthält ein Datenelement und einen Zeiger auf den nächsten Knoten. Vorteil: Einfügen und Löschen von Elementen ist effizient möglich. Nachteil: effizienter Zugriff nur auf den Nachfolger eines gegebenen Elements, d.h. Zugriffsreihenfolge ist nicht beliebig.&lt;br /&gt;
&lt;br /&gt;
==== Stabilität ====&lt;br /&gt;
&lt;br /&gt;
Ein Sortierverfahren heißt ''stabil'' falls die relative Reihenfolge gleicher Schlüssel durch die Sortierung nicht verändert wird.&lt;br /&gt;
&lt;br /&gt;
Beispiel: Sortiere eine Liste von Paaren &amp;lt;tt&amp;gt;[(3,7), (4,2), (4,1), (2,2), (2,8)]&amp;lt;/tt&amp;gt;, wobei die Reihenfolge nur durch das erste Element (Schlüsselelement) jeden Paares festgelegt wird.&lt;br /&gt;
Dann erzeugt ein stabiles Sortierverfahren die Ausgabe&lt;br /&gt;
 [(2,2), (2,8), (3,7), (4,2), (4,1)]&lt;br /&gt;
während die Ausgabe&lt;br /&gt;
 [(2,2), (2,8), (3,7), (4,1), (4,2)]&lt;br /&gt;
nicht stabil ist (die Paare &amp;lt;tt&amp;gt;(4,1), (4,2)&amp;lt;/tt&amp;gt; sind vertauscht).&lt;br /&gt;
&lt;br /&gt;
==== Charakterisierung der Effizienz von Algorithmen ====&lt;br /&gt;
  &lt;br /&gt;
:(a) Komplexität O(1), O(n), etc. wird in Kapitel [[Effizienz]] erklärt.&lt;br /&gt;
:(b) Zählen der notwendigen Vergleiche&lt;br /&gt;
:(c) Messen der Laufzeit mit 'timeit' (auf identischen Daten)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Rekursive Beziehungen'''&lt;br /&gt;
zerlegt die ursprünglichen Probleme in kleinere Probleme und wendet den Algorithmus auf die kleineren Probleme an; daraufhin werden die Teilprobleme zur Lösung des Gesamtproblems verwendet. &lt;br /&gt;
d.h. Laufzeit (operativer Vergleich) für N Eingaben hängt von der Laufzeit der Eingaben für die Teilprobleme &lt;br /&gt;
&lt;br /&gt;
'''Aufwand'''&lt;br /&gt;
&lt;br /&gt;
(i) rekursives/ lineares Durchlaufen der Eingabedaten, Bearbeitung einzelner Elemente&lt;br /&gt;
&lt;br /&gt;
 C(N)= C(N-1)+ N ;  N&amp;gt;1, C(1)= 1             +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
     = C(N-2) +(N-1)+ N                      | 7  | 3 | 2 | 5 | 6 | 8 | 1 | 4 | 2 |  &lt;br /&gt;
     = C(N-3) + (N-2) + (N-1) + N            +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
     = ...                                         ________________________/&lt;br /&gt;
     = C(1) + 2+...+(N-1) +N                     /&lt;br /&gt;
                                               +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
        N(N+1)   N²                            | 1 | 3 | 2 | 5 | 6 | 8 | 7 | 4 | 2 |  &lt;br /&gt;
      = -----  ~ --                            +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
          2       2                   &lt;br /&gt;
            &lt;br /&gt;
                                                      &lt;br /&gt;
                                               &lt;br /&gt;
   &lt;br /&gt;
&lt;br /&gt;
(ii) rekursives halbieren der Menge der Eingabedaten&lt;br /&gt;
    &lt;br /&gt;
 C(N)= C(N/2)+1 ; N&amp;gt;1, C(1)=0&lt;br /&gt;
 Aus Gründen der Einfachheit sei N  = 2n&lt;br /&gt;
 &lt;br /&gt;
 C(N)= C(2^n)= C(&amp;lt;math&amp;gt;2^{n-1}&amp;lt;/math&amp;gt;) + 1       &lt;br /&gt;
                     &lt;br /&gt;
              = C(&amp;lt;math&amp;gt;2^{n-1}&amp;lt;/math&amp;gt;) + 1 + 1   &lt;br /&gt;
              = ...                    &lt;br /&gt;
                                       &lt;br /&gt;
              = C(&amp;lt;math&amp;gt;2^0&amp;lt;/math&amp;gt;) + n               &lt;br /&gt;
              = n                    &lt;br /&gt;
              = &amp;lt;math&amp;gt;log_2 N&amp;lt;/math&amp;gt;                 &lt;br /&gt;
 +---+---+---+---+-|-+---+---+---+---+ &lt;br /&gt;
 |   |    |   |   |   |   |    |   |   |&lt;br /&gt;
 +---+---+---+---+-|-+---+---+---+---+&lt;br /&gt;
 +---+---+---+---+&lt;br /&gt;
 |   |    |   |   |&lt;br /&gt;
 +---+---+---+---+&lt;br /&gt;
 +---+---+        +---+&lt;br /&gt;
 |   |    |  -&amp;gt;   |   |&lt;br /&gt;
 +---+---+        +---+&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
(iii) rekursives halbieren, lineare Bearbeitung, jedes Elements&lt;br /&gt;
  &lt;br /&gt;
 C(N)= 2C(N/2)+ N; N&amp;gt;1, C(1)= 0&lt;br /&gt;
 Sei N= &amp;lt;math&amp;gt;2^n&amp;lt;/math&amp;gt;&lt;br /&gt;
 C(N)= C(&amp;lt;math&amp;gt;2^n&amp;lt;/math&amp;gt;)= 2C (&amp;lt;math&amp;gt;2^{n-1}&amp;lt;/math&amp;gt;)+ &amp;lt;math&amp;gt;2^n&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;=&amp;gt; &amp;lt;math&amp;gt; \cfrac{C(2^n)}{2^n}&amp;lt;/math&amp;gt; = &amp;lt;math&amp;gt; \cfrac{2C(2^{n-1})}{2^{n-1}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 = &amp;lt;math&amp;gt; \cfrac{2C(2^{n-2})+2^{n-1}}{2^{n-1}}+1&amp;lt;/math&amp;gt;&lt;br /&gt;
 = &amp;lt;math&amp;gt; \cfrac{2C(2^{n-2})}{2^{n-2}}+1 +1&amp;lt;/math&amp;gt;&lt;br /&gt;
 =...&lt;br /&gt;
 = n&lt;br /&gt;
&amp;lt;=&amp;gt; C(&amp;lt;math&amp;gt;2^n&amp;lt;/math&amp;gt;)= &amp;lt;math&amp;gt;2^n&amp;lt;/math&amp;gt; * n&lt;br /&gt;
&amp;lt;=&amp;gt; C= N log&amp;lt;math&amp;gt;_2&amp;lt;/math&amp;gt;N&lt;br /&gt;
&lt;br /&gt;
==Selection Sort==&lt;br /&gt;
&lt;br /&gt;
===Algorithmus===&lt;br /&gt;
&lt;br /&gt;
 array = [...]  # zu sortierendes Array&lt;br /&gt;
 &lt;br /&gt;
 for i in range(len(array)-1):&lt;br /&gt;
    min = i&lt;br /&gt;
    for j in range(i+1, len(array)):&lt;br /&gt;
       if a[j]&amp;lt; a[min]:&lt;br /&gt;
           min = j&lt;br /&gt;
    a[i], a[min] = a[min], a[i]  # Vertausche a[i] mit dem kleinsten rechts befindlichen Element&lt;br /&gt;
                                 # Elemente links von a[i] und a[i] selbst befinden sich nun in ihrer endgültigen Position&lt;br /&gt;
&lt;br /&gt;
Beispiel: Sortieren der Liste &amp;lt;tt&amp;gt;[S,O,R,T,I,N,G]&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
 erste Iteration der äußeren Schleife, Zustand ''vor'' dem Vertauschen:&lt;br /&gt;
  i=0                     min&lt;br /&gt;
 +---+---+---+---+---+---+---+&lt;br /&gt;
 | S | O | R | T | I | N | G |&lt;br /&gt;
 +---+---+---+---+---+---+---+&lt;br /&gt;
 &lt;br /&gt;
 erste Iteration der äußeren Schleife, Zustand ''nach'' dem Vertauschen:&lt;br /&gt;
 +---|---+---+---+---+---+---+&lt;br /&gt;
 | G | O | R | T | I | N | S |&lt;br /&gt;
 +---|---+---+---+---+---+---+&lt;br /&gt;
 &lt;br /&gt;
 zweite Iteration der äußeren Schleife:&lt;br /&gt;
      i=1         min&lt;br /&gt;
 +---|---+---+---+---+---+---+&lt;br /&gt;
 | G | O | R | T | I | N | S |&lt;br /&gt;
 +---|---+---+---+---+---+---+&lt;br /&gt;
 &lt;br /&gt;
 weitere Iterationen:&lt;br /&gt;
          i=2         min&lt;br /&gt;
 +---+---|---+---+---+---+---+&lt;br /&gt;
 | G | I | R | T | O | N | S |&lt;br /&gt;
 +---+---|---+---+---+---+---+ &lt;br /&gt;
 &lt;br /&gt;
              i=3 min&lt;br /&gt;
 +---+---+---|---+---+---+---+&lt;br /&gt;
 | G | I | N | T | O | R | S |&lt;br /&gt;
 +---+---+---|---+---+---+---+ &lt;br /&gt;
 &lt;br /&gt;
                  i=4 min&lt;br /&gt;
 +---+---+---+---+---+---+---+&lt;br /&gt;
 | G | I | N | O | T | R | S |&lt;br /&gt;
 +---+---+---+---+---+---+---+  &lt;br /&gt;
 ...&lt;br /&gt;
&lt;br /&gt;
===Laufzeit===&lt;br /&gt;
&lt;br /&gt;
Da in jeder Iteration der ''inneren'' Schleife ein Vergleich &amp;lt;tt&amp;gt;a[j]&amp;lt; a[min]&amp;lt;/tt&amp;gt; durchgeführt wird, ist die Anzahl der Vergleiche ein gutes Maß für den Aufwand des Algorithmus und damit für die Laufzeit. Sei C(N) die Anzahl der notwendigen Vergleiche, um ein Array der Größe N zu sortieren. Die Arbeitsweise des Algorithmus kann dann so beschrieben werden: Führe N-1 Vergleiche aus, bringe das kleinste Element an die erste Stelle, und fahre mit dem Sortieren des Rest-Arrays (Größe N-1) rechts des ersten Elements fort. Dafür sind nach Definition noch C(N-1) Vergleiche nötig. Es gilt also:&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = C(N-1) + (N-1)&amp;lt;/math&amp;gt;&lt;br /&gt;
C(N-1) können wir nach der gleichen Formel einsetzen, und erhalten:&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = C(N-2) + (N-2) + (N-1)&amp;lt;/math&amp;gt;&lt;br /&gt;
Wir können in dieser Weise weiter fortfahren. Bei C(1) wird das Einsetzen beendet, denn für ein Array der Länge 1 sind keine Vergleiche mehr nötig, also C(1) = 0. Wir erhalten somit&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = C(N-3) + (N-3) + (N-2) + (N-1)&amp;lt;/math&amp;gt;&lt;br /&gt;
:::&amp;lt;math&amp;gt;...&amp;lt;/math&amp;gt;&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = C(1) + 1 + 2 + ...+ (N-2)+ (N-1)&amp;lt;/math&amp;gt;&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = 0 + 1 + 2 + ...+ (N-2)+ (N-1)&amp;lt;/math&amp;gt;&lt;br /&gt;
Nach der Gaußschen Summenformel ist dies&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = \frac {(N-1)N}{2}\approx \cfrac {(N^2)}{2}&amp;lt;/math&amp;gt; (für große N).&lt;br /&gt;
&lt;br /&gt;
In jedem Durchlauf der äußeren Schleife werden außerdem zwei Elemente ausgetauscht. Es gilt für die Anzahl der Austauschoperationen&lt;br /&gt;
:::&amp;lt;math&amp;gt;A(N)= N-1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Stabilität===&lt;br /&gt;
&lt;br /&gt;
Selection Sort ist stabil, wenn die Vergleiche durch &amp;lt;tt&amp;gt;a[j] &amp;lt; a[min]&amp;lt;/tt&amp;gt; erfolgen, weil dann immer das erste Element mit einem gegebenen Schlüssel als erster nach vorn gebracht wird. Bei Vergleichen &amp;lt;tt&amp;gt;a[j] &amp;lt;= a[min]&amp;lt;/tt&amp;gt; wird hingegen das letzte Element zuerst nach vorn gebracht, somit ist Selection Sort dann nicht stabil.&lt;br /&gt;
&lt;br /&gt;
==Insertion Sort==&lt;br /&gt;
&lt;br /&gt;
* wird in der Übungsgruppe behandelt, siehe auch in der [http://de.wikipedia.org/wiki/Insertionsort WikiPedia]&lt;br /&gt;
* Erweiterung: [http://en.wikipedia.org/wiki/Shell_sort Shell sort]&lt;br /&gt;
&lt;br /&gt;
== Mergesort ==&lt;br /&gt;
&lt;br /&gt;
===Algorithmus ===&lt;br /&gt;
&lt;br /&gt;
Zugrunde liegende Idee: &lt;br /&gt;
* Zerlege das Problem in zwei möglichst gleich große Teilprobleme (&amp;quot;Teile und herrsche&amp;quot;-Prinzip -- divide and conquer)&lt;br /&gt;
* Löse die Teilprobleme rekursiv&lt;br /&gt;
* Führe die Teillösungen über Mischen (merging) in richtig sortierter Weise zusammen.&lt;br /&gt;
Der Algorithmus besteht somit aus zwei Teilen&lt;br /&gt;
&lt;br /&gt;
====Zusammenführen -- merge====&lt;br /&gt;
&lt;br /&gt;
a und b sind zwei sortierte Listen, die in eine sortierte Ergebnisliste kombiniert werden.&lt;br /&gt;
&lt;br /&gt;
 def merge(a,b):&lt;br /&gt;
     c = []   # zunächst leere Ergebnisliste &lt;br /&gt;
     i, j = 0, 0&lt;br /&gt;
     while i &amp;lt; len(a) and j &amp;lt; len(b):&lt;br /&gt;
         # wähle des kleinste der noch nicht angefügten Elemente&lt;br /&gt;
         if a[i] &amp;lt;= b[j]:&lt;br /&gt;
              c.append(a[i])&lt;br /&gt;
              i += 1&lt;br /&gt;
         else:&lt;br /&gt;
              c.append(b[j])&lt;br /&gt;
              j += 1&lt;br /&gt;
    # eine Liste ist jetzt aufgebraucht =&amp;gt; der Rest der anderen wird einfach an c angehängt&lt;br /&gt;
    if i &amp;lt; len(a):&lt;br /&gt;
        c += a[i:]&lt;br /&gt;
    else:&lt;br /&gt;
        c += b[j:]&lt;br /&gt;
    return c&lt;br /&gt;
&lt;br /&gt;
====rekursives Sortieren====&lt;br /&gt;
&lt;br /&gt;
 def mergeSort(a):  # a ist das zu sortierende Array&lt;br /&gt;
     if len(a) &amp;lt;= 1:&lt;br /&gt;
         return a   # Rekursionsabschluß: leere Arrays und Arrays mit einem Element müssen nicht sortiert werden&lt;br /&gt;
     else:&lt;br /&gt;
         left  = a[:len(a)/2]   # linkes Teilarray&lt;br /&gt;
         right = a[len(a)/2:]   # rechtes Teilarray&lt;br /&gt;
         leftSorted  = mergeSort(left)  # rekursives Sortieren der Teilarrays&lt;br /&gt;
         rightSorted = mergeSort(right) # ...&lt;br /&gt;
         return merge(leftSorted, rightSorted)  # Zusammenführen der Teilarrays&lt;br /&gt;
&lt;br /&gt;
Bei der Sortierung mit Mergesort wird das Array immer in zwei Teile geteilt. → Es entsteht ein Binärbaum der Tiefe &amp;lt;math&amp;gt;\log_2 N&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Beispiel: Sortieren der Liste &amp;lt;tt&amp;gt;[S,O,R,T,I,N,G]&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Der Algorithmus läuft in der folgenden Skizze zunächst rekursiv von unten nach oben (Zerlegen in Teillisten), danach werden die sortierten Teillisten von oben nach unten zusammengeführt (diese sortierten Teillisten sind in der Skizze dargestellt).&lt;br /&gt;
&lt;br /&gt;
 Schritt 0:&lt;br /&gt;
  S 0 R T I N G     S    O R    T I    N     G    #Arraylänge: N/8    Vergleiche: 0&lt;br /&gt;
 Schritt 1:          \  /   \  /   \  /     /&lt;br /&gt;
  OS RT IN G          OS     RT     IN     /      #Arraylänge: N/4    Vergleiche: 3 * 2 = 6&lt;br /&gt;
 Schritt 2:             \    /        \   /       &lt;br /&gt;
  ORST GIN               ORST          GIN        #Arraylänge: N/2    Vergleiche: 4 + 3 = 7&lt;br /&gt;
                            \         /&lt;br /&gt;
 Schritt3:                   \       /&lt;br /&gt;
  GINORST                     GINORST             #Arraylänge: N      Vergleiche: N     = 7&lt;br /&gt;
&lt;br /&gt;
===Laufzeit ===&lt;br /&gt;
&lt;br /&gt;
Man erkennt an der Skizze, dass der Rekursionsbaum für ein Array der Länge N die Tiefe log N hat. Auf jeder Ebene werden weniger als N Vergleiche ausgeführt, so dass insgesamt weniger als N*log N Vergleiche benötigt werden. Dies ist natürlich wesentlich effizienter als die (N-1)*N/2 Vergleiche von Selection Sort. Mathematisch exakt kann man die Anzahl der Vergleiche durch die folgende Rekursionsformel berechnen:&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = C(\lfloor N/2\rfloor) + C(\lceil N/2\rceil) + N&amp;lt;/math&amp;gt;&lt;br /&gt;
Der Aufwand ergibt sich aus dem Aufwand für die beiden Teilprobleme plus dem Aufwand für N Vergleiche beim Zusammenführen der sortierten Teillisten. Dabei stehen die Zeichen &amp;lt;math&amp;gt;\lfloor \rfloor&amp;lt;/math&amp;gt; und &amp;lt;math&amp;gt;\lceil \rceil&amp;lt;/math&amp;gt; für abrunden bzw. aufrunden, weil ein Problem mit ungeradem N nicht in zwei exakt gkeiche Teile geteilt werden kann. Um diese Komplikation zu vermeiden, beschränken wir uns im folgenden auf den Fall &amp;lt;math&amp;gt;N = 2^n&amp;lt;/math&amp;gt; (mit etwas höherem Aufwand kann man zeigen, dass diese Einschränkung nicht notwendig ist und die Resultate für alle N gelten). Die vereinfachte Aufwandsformel lautet:&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = 2 C(N/2) + N&amp;lt;/math&amp;gt;&lt;br /&gt;
Durch Einsetzen der Formel für N/2 erhalten wir:&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = 2 (2 C(N/4) + N/2) + N = 4 C(N/4) + N + N&amp;lt;/math&amp;gt;&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = 4 (2 C(N/8) + N/4) + N + N = 8 C(N/8) + N + N + N&amp;lt;/math&amp;gt;&lt;br /&gt;
:::&amp;lt;math&amp;gt;...&amp;lt;/math&amp;gt;&lt;br /&gt;
Die Rekursion endet, weil für ein Array der Größe &amp;lt;math&amp;gt;N=1&amp;lt;/math&amp;gt; keine Vergleiche mehr benötigt werden, also &amp;lt;math&amp;gt;C(1) = 0&amp;lt;/math&amp;gt; gilt. Mit &amp;lt;math&amp;gt;N=2^n&amp;lt;/math&amp;gt; ist dies aber gerade nach &amp;lt;math&amp;gt;n = \log_2 N&amp;lt;/math&amp;gt; Zerlegungen der Fall. Merge Sort benötigt also&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = N + ... + N = n \cdot N = N\cdot \log_2 N&amp;lt;/math&amp;gt;&lt;br /&gt;
Vergleiche.&lt;br /&gt;
&lt;br /&gt;
===Weitere Eigenschaften von MergeSort ===&lt;br /&gt;
&lt;br /&gt;
* Mergesort ist '''stabil''': wegen des Vergleichs &amp;lt;tt&amp;gt;a[i] &amp;lt;= b[j]&amp;lt;/tt&amp;gt; wird die Position gleicher Schlüssel im Algorithmus &amp;lt;tt&amp;gt;merge(a,b)&amp;lt;/tt&amp;gt; nicht verändert -- bei gleichem Schlüssel hat, wie gefordert, das linke Element Vorrang.&lt;br /&gt;
* Mergesort ist '''unempfindlich gegenüber der ursprünglichen Reihenfolge der Eingabedaten'''. Grund dafür ist&lt;br /&gt;
** die vollständige Aufteilung des Ausgangsarrays in Arrays der Länge 1 und&lt;br /&gt;
** dass &amp;lt;tt&amp;gt;merge(a,b)&amp;lt;/tt&amp;gt; die Vorsortierung nicht ausnutzt, d.h. die Komplexität von &amp;lt;tt&amp;gt;merge(a,b)&amp;lt;/tt&amp;gt; ist sortierungsunabhängig.&lt;br /&gt;
* Diese Eigenschaft kann unerwünscht sein, wenn ein Teil des Arrays oder gar das ganze Array schon sortiert ist. Es wird nämlich in jedem Fall das ganze Array neu sortiert.&lt;br /&gt;
* Merge Sort eignet sich für das Sortieren von '''verketteten Listen''', weil die Listenelemente stets von vorn nach hinten durchlaufen werden. In diesem Fall muss &amp;lt;tt&amp;gt;merge(a, b)&amp;lt;/tt&amp;gt; keine neue Liste &amp;lt;tt&amp;gt;c&amp;lt;/tt&amp;gt; für das Ergebnis anlegen, sondern kann einfach die Verkettung der Listenelemente von &amp;lt;tt&amp;gt;a&amp;lt;/tt&amp;gt; und &amp;lt;tt&amp;gt;b&amp;lt;/tt&amp;gt; entsprechend anpassen. In diesem Sinne arbeitet Merge Sort auf verketten Listen &amp;quot;in place&amp;quot;, d.h. es wird kein zusätzlicher Speicher benötigt.&lt;br /&gt;
* Im Gegensatz dazu benötigt &amp;lt;tt&amp;gt;merge(a,b)&amp;lt;/tt&amp;gt; zusätzlichen Speicher für das Ergebnis &amp;lt;tt&amp;gt;c&amp;lt;/tt&amp;gt;, wenn die Daten in einem Array gegeben sind.&lt;br /&gt;
&lt;br /&gt;
== Quicksort ==&lt;br /&gt;
&lt;br /&gt;
* Quicksort wurde in den 60er Jahren von Charles Antony Richard Hoare [http://de.wikipedia.org/wiki/C._A._R._Hoare] entwickelt. Es gibt viele Implementierungen von Quicksort, vgl. [http://de.wikipedia.org/wiki/Quicksort].&lt;br /&gt;
* Dieser Algorithmus gehört zu den &amp;quot;Teile und herrsche&amp;quot;-Algorithmen (divide-and-conquer) und ist der Standardalgorithmus für Sortieren.&lt;br /&gt;
* Im Gegensatz zu Merge Sort wird das Problem aber nicht immer in zwei fast gleich große Teilprobleme zerlegt. Dadurch vermeidet man, dass zusätzlicher Speicher benötigt wird (Quick Sort arbeitet auch für Arrays &amp;quot;in place&amp;quot;). Allerdings erkauft man sich dies dadurch, dass Quick Sort bei ungünstigen Eingaben (die Bedeutung von &amp;quot;ungünstig&amp;quot; ist je nach Implementation verschieden) nicht effizient arbeitet. Da solche Eingaben jedoch in der Praxis fast nie vorkommen, tut dies der Beliebtheit von Quicksort keinen Abbruch.&lt;br /&gt;
&lt;br /&gt;
=== Algorithmus===&lt;br /&gt;
&lt;br /&gt;
Wie Merge Sort arbeitet Quick Sort rekursiv. Hier werden die Daten allerdings zuerst vorbereitet (in der Funktion &amp;lt;tt&amp;gt;partition&amp;lt;/tt&amp;gt;), und danach erfolgt der rekursive Aufruf:&lt;br /&gt;
&lt;br /&gt;
 def quicksort(a, l, r): &lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;a ist das zu sortierende Array, &lt;br /&gt;
        l und r sind die linke und rechte Grenze des zu sortierenden Bereichs&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
      if r &amp;gt; l:                     # Rekursionsabschluss: wenn r &amp;lt;= l, ist der Bereich leer und muss nicht mehr sortiert werden&lt;br /&gt;
          i = partition(a, l, r)    # i ist der Index des sog. Pivot-Elements (s. u.)&lt;br /&gt;
          quicksort(a, l, i-1)      # rekursives Sortieren der beiden Teilarrays&lt;br /&gt;
          quicksort(a, i+1, r)      # ...&lt;br /&gt;
&lt;br /&gt;
Der Schlüssel des Algorithmus ist offensichtlich die Funktion &amp;lt;tt&amp;gt;partition&amp;lt;/tt&amp;gt;. Diese wählt ein Element des Arrays aus (das Pivot-Element) und bringt es an die richtige Stelle (also an den Index &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt;, der von &amp;lt;tt&amp;gt;partition&amp;lt;/tt&amp;gt; zurückgegeben wird). Ausserdem stellt sie sicher, dass alle Elemente in der linken Teilliste (Index &amp;lt; &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt;) kleiner als &amp;lt;tt&amp;gt;a[i]&amp;lt;/tt&amp;gt;, und alle Elemente in der rechten Teilliste größer also &amp;lt;tt&amp;gt;a[i]&amp;lt;/tt&amp;gt; sind:&lt;br /&gt;
# &amp;lt;math&amp;gt;a[i]&amp;lt;/math&amp;gt; ist sortiert, d.h. dieses Element ist am endgültigen Platz.&lt;br /&gt;
# &amp;lt;math&amp;gt;\forall x \in \left\{ a \left[ l \right] , ... a \left[ i-1 \right] \right\} : x \leq a \left[ i \right]&amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt;\forall x \in \left\{ a \left[ i+1 \right], ... a \left[ r \right] \right\} : x \geq a \left[ i \right]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
          l                               r&lt;br /&gt;
        +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 Array: |   |   |   |   |\\\|   |   |   |   |&lt;br /&gt;
        +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
         \______  _____/  i  \______  _____/ &lt;br /&gt;
                \/                  \/&lt;br /&gt;
             &amp;lt;=a[i]               &amp;gt;=a[i]             (a[i] ist das Pivot-Element)&lt;br /&gt;
&lt;br /&gt;
Die Position von &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt; richtet sich also offensichtlich danach, wie viele Elemente im Bereich &amp;lt;tt&amp;gt;l&amp;lt;/tt&amp;gt; bis &amp;lt;tt&amp;gt;r&amp;lt;/tt&amp;gt; kleiner bzw. größer als das gewählte Pivot-Element sind. Der Wahl eines guten Pivot-Elements kommt demnach eine große Bedeutung zu (s.u.). &lt;br /&gt;
&lt;br /&gt;
In der einfachsten Version wird &amp;lt;tt&amp;gt;partition&amp;lt;/tt&amp;gt; wie folgt definiert:&lt;br /&gt;
&lt;br /&gt;
 def partition(a, l, r):&lt;br /&gt;
     pivot = a[r]     # Pivot-Element. Hier wird willkürlich das letzte Element verwendet.&lt;br /&gt;
     i  = l           # i und j sind Laufvariablen&lt;br /&gt;
     j  = r - 1&lt;br /&gt;
 &lt;br /&gt;
     while True:&lt;br /&gt;
         while a[i] &amp;lt;= pivot and i &amp;lt; r:&lt;br /&gt;
             i += 1               # finde von links das erste Element &amp;gt; pivot&lt;br /&gt;
         while a[j] &amp;gt;= pivot and j &amp;gt; l:&lt;br /&gt;
             j -= 1               # finde von rechts den ersten Eintrag &amp;lt;= pivot&lt;br /&gt;
         if i &amp;gt;= j: break         # keine weiteren Elemente zum Tauschen =&amp;gt; Schleife beenden      &lt;br /&gt;
         a[i], a[j] = a[j], a[i]  # a[i] und a[j] sind beide auf der falschen Seite des Pivot =&amp;gt; vertausche sie&lt;br /&gt;
     if a[i] &amp;gt; pivot:&lt;br /&gt;
         a[i], a[r] = a[r], a[i]&lt;br /&gt;
     return i&lt;br /&gt;
&lt;br /&gt;
Die folgende Skizze verdeutlicht das Austauschen&lt;br /&gt;
&lt;br /&gt;
                                          p&lt;br /&gt;
        +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 Array: |   |   |   |   |   |   |   |   |\\\|&lt;br /&gt;
        +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
        ------&amp;gt; a[i]&amp;gt;p          a[j]&amp;lt;p &amp;lt;-----&lt;br /&gt;
                  |               |&lt;br /&gt;
                  +---------------+&lt;br /&gt;
       Diese zwei Elemente werden ausgetauscht.&lt;br /&gt;
 &lt;br /&gt;
Dies wird wiederholt, bis sich die Zeiger treffen oder einander überholt haben. Am Schluss wird das Pivot-Element an die richtige Stelle verschoben:&lt;br /&gt;
 &lt;br /&gt;
                          p&lt;br /&gt;
        +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 Array: |   |   |   |   |\\\|   |   |   |   |&lt;br /&gt;
        +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
                          i&lt;br /&gt;
        -----------------&amp;gt; &amp;lt;-----------------&lt;br /&gt;
  &lt;br /&gt;
Beispiel: Partitionieren des Arrays &amp;lt;tt&amp;gt;[A,S,O,R,T,I,N,G,E,X,A,M,P,L,E]&amp;lt;/tt&amp;gt; mit Pivot 'E'.&lt;br /&gt;
&lt;br /&gt;
  l,i --&amp;gt;                                          &amp;lt;-- j   r&lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 | A | S | O | R | T | I | N | G | E | X | A | M | P | L | E |&lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 &lt;br /&gt;
       i &amp;lt;--------- Vertauschen ---------&amp;gt; j               r&lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 | A | S | O | R | T | I | N | G | E | X | A | M | P | L | E |&lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 &lt;br /&gt;
           i &amp;lt;-------------------&amp;gt; j                       r&lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 | A | A | O | R | T | I | N | G | E | X | S | M | P | L | E |&lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 &lt;br /&gt;
           j   i                                           r&lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 | A | A | E | R | T | I | N | G | O | X | S | M | P | L | E |   --&amp;gt; Hier wird die &lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+       Schleife verlassen.&lt;br /&gt;
 &lt;br /&gt;
           j   i &amp;lt;---------------------------------------&amp;gt; r&lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 | A | A | E | R | T | I | N | G | O | X | S | M | P | L | E |   &lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 &lt;br /&gt;
               i                                           r&lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 | A | A | E | E | T | I | N | G | O | X | S | M | P | L | R |   --&amp;gt; Hier wird partition() beendet.&lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Weitere ausführliche Erklärungen der Implementation findet man bei Sedgewick.&lt;br /&gt;
&lt;br /&gt;
=== Laufzeit===&lt;br /&gt;
&lt;br /&gt;
Wir müssen hier den schlechtesten und den typischen Fall unterscheiden. Der schlechteste Fall tritt ein, wenn das Array bereits sortiert ist. Dann ist das Pivot-Element immer bereits am richtigen Platz, so dass &amp;lt;tt&amp;gt;partition(a, l, r)&amp;lt;/tt&amp;gt; stets den Index &amp;lt;tt&amp;gt;i = r&amp;lt;/tt&amp;gt; zurück. Daher wird das Array niemals in zwei etwa gleichgroße Teile zerlegt. Die Anzahl der Vergleiche ergibt sich als&lt;br /&gt;
&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = (N+1) + C(N-1) + C(0)&amp;lt;/math&amp;gt;&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(0) = 0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
mit (N+1) Vergleichen in &amp;lt;tt&amp;gt;partition()&amp;lt;/tt&amp;gt;. Durch sukzessives Einsetzen erhalten wir:&lt;br /&gt;
&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = (N+1) + (N) + (N-1) + ... + 1 = (N+1) N / 2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In diesem Fall ist Quick Sort also nicht schneller als Selection Sort. Wir beschreiben mögliche Verbesserungen unten. Im typischen Fall (wenn nämlich das Array zufällig sortiert ist) sieht die Situation wesentlich besser aus. Bei zufälliger Sortierung wird jeder Index mit gleicher Wahrscheinlichkeit zur Pivot-Position. Wir mitteln deshalb über alle möglichen Positionen:&lt;br /&gt;
&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = (N+1) + \frac{1}{N} \sum_{k=1}^{N} \left[ C(k-1) + C(N-k) \right]&amp;lt;/math&amp;gt; für &amp;lt;math&amp;gt; N&amp;gt;0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
wobei  &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; über alle möglichen Teilungspunkte läuft. Die Summe (der mittlere Aufwand über alle möglichen Zerlegungen) kann vereinfacht werden zu&lt;br /&gt;
:::&amp;lt;math&amp;gt;\frac{1}{N} \sum_{k=1}^{N} \left[ C(k-1) + C(N-k) \right] = 2 \frac{1}{N} \sum_{k=1}^{N} C(k-1) &amp;lt;/math&amp;gt;&lt;br /&gt;
Die Auflösung der Formel ist etwas trickreich. Wir multiplizieren zunächst beide Seiten mit N:&lt;br /&gt;
&lt;br /&gt;
:::&amp;lt;math&amp;gt;&lt;br /&gt;
N \cdot C(N) = N \left[ (N+1) + \frac{2}{N} \sum_{k=1}^{N} C(k-1) \right] = N (N+1) + 2\; \sum_{k=1}^{N} C(k-1)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durch die Substitution &amp;lt;math&amp;gt;N \rightarrow N-1&amp;lt;/math&amp;gt; erhalten wir die entsprechende Formel für N-1:&lt;br /&gt;
&lt;br /&gt;
:::&amp;lt;math&amp;gt;&lt;br /&gt;
(N-1) \cdot C(N-1) = (N-1) N + 2\; \sum_{k=1}^{N-1} C(k-1)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Wir subtrahieren die Formel für N-1 von der Formel für N und eliminieren dadurch die Summe (nur der letzte Summend der ersten Summe bleibt übrig):&lt;br /&gt;
:::&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{rcl}&lt;br /&gt;
N \cdot C(N) - (N-1) \cdot C(N-1) &amp;amp;=&amp;amp; N(N+1) + 2\;\sum_{k=1}^{N} C(k-1)  - (N-1) N - 2\;\sum_{k=1}^{N-1} C(k-1)\\&lt;br /&gt;
&amp;amp;&amp;amp;\\&lt;br /&gt;
N \cdot C(N) - (N-1) \cdot C(N-1) &amp;amp;=&amp;amp; N(N+1) - (N-1) N + 2 C(N-1)&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
Durch Vereinfachen erhalten wir die rekurrente Beziehung&lt;br /&gt;
:::&amp;lt;math&amp;gt;&lt;br /&gt;
N \cdot C(N) = (N+1)\cdot C(N-1) + 2 N&amp;lt;/math&amp;gt;&lt;br /&gt;
Wir teilen jetzt beide Seiten durch &amp;lt;math&amp;gt;(N+1)N&amp;lt;/math&amp;gt;&lt;br /&gt;
:::&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{C(N)}{N+1} = \frac{C(N-1)}{N} + \frac{2}{N+1} &amp;lt;/math&amp;gt;&lt;br /&gt;
Sukzessives Einsetzen der Formel für &amp;lt;math&amp;gt; C(N-1), C(N-2) &amp;lt;/math&amp;gt; etc. bis &amp;lt;math&amp;gt;C(1)=0&amp;lt;/math&amp;gt; liefert&lt;br /&gt;
:::&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{C(N)}{N+1} = \frac{C(N-2)}{N-1} + \frac{2}{N} + \frac{2}{N+1} = \frac{C(2)}{3} + \sum_{k=3}^N\frac{2}{k+1} &amp;lt;/math&amp;gt;&lt;br /&gt;
Für hinreichend große N kann die Summe sehr genau durch ein Integral approximiert werden. Der konstanten Term kann vernachlässigt werden:&lt;br /&gt;
:::&amp;lt;math&amp;gt; &lt;br /&gt;
\frac{C(N)}{N+1} \approx 2 \sum_{k=3}^{N} \frac{1}{k+1} \approx 2 \int_1^N \frac{1}{k} dk = 2 \cdot \ln(N)&amp;lt;/math&amp;gt;&lt;br /&gt;
Somit benötigt Quick Sort im typischen Fall&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N)\approx 2 N\cdot\ln(N) \approx 1.38 N\cdot\log_2(N)&amp;lt;/math&amp;gt;&lt;br /&gt;
Vergleiche. Quick Sort ist demnach etwa genauso schnell wie Merge Sort (in der Praxis sogar etwas schneller, da die innere Schleife von Quick Sort etwas einfacher ist).&lt;br /&gt;
&lt;br /&gt;
=== Verbesserungen des Quicksort-Algorithmus ===&lt;br /&gt;
&lt;br /&gt;
==== Beseitigung der Rekursion ====&lt;br /&gt;
Eine Verbesserung beseitigt die Rekursion durch Verwendung eines Stacks. Nach jeder Partitionierung wird das größere Teilintervall auf dem Stack abgelegt und das kleinere Teilintervall direkt weiterverarbeitet (hierdurch wird sichergestellt, dass die maximale Größe des Stacks minimiert wird).&lt;br /&gt;
&lt;br /&gt;
 def quicksortNonRecursive(a, l, r):&lt;br /&gt;
     stack = [(l,r)]  # initialisiere den Stack&lt;br /&gt;
     while len(stack) &amp;gt; 0:&lt;br /&gt;
         if r &amp;gt; l:&lt;br /&gt;
             i = partition(a, l, r)&lt;br /&gt;
             if (i-l) &amp;gt; (r-i):&lt;br /&gt;
                 stack.append((l,i-1))&lt;br /&gt;
                 l = i+1&lt;br /&gt;
             else:&lt;br /&gt;
                 stack.append((i+1, r))&lt;br /&gt;
                 r = i-1&lt;br /&gt;
         else:&lt;br /&gt;
             l, r = stack.pop()&lt;br /&gt;
&lt;br /&gt;
Die ist die Methode der ''Endrekursionsbeseitigung'', die wir im Kapitel [[Iteration versus Rekursion]] ausführlich behandeln. Die folgende Skizze verdeutlicht die Verwendung des Stacks.&lt;br /&gt;
&lt;br /&gt;
 +---+---+---+---+---+---+---+&lt;br /&gt;
 | Q | U | I | C | K | S | O |&lt;br /&gt;
 +---+---+---+---+---+---+---+&lt;br /&gt;
 &lt;br /&gt;
              &lt;br /&gt;
 +---+---+---+===+---+---+---+&lt;br /&gt;
 | K | C | I |=O=| Q | S | U |&lt;br /&gt;
 +---+---+---+===+---+---+---+&lt;br /&gt;
                  \_________/&lt;br /&gt;
                      push&lt;br /&gt;
 &lt;br /&gt;
 +---+===+---+&lt;br /&gt;
 | C |=I=| K |&lt;br /&gt;
 +---+===+---+&lt;br /&gt;
          \_/&lt;br /&gt;
          push&lt;br /&gt;
 &lt;br /&gt;
 +===+&lt;br /&gt;
 |=C=|&lt;br /&gt;
 +===+&lt;br /&gt;
 &lt;br /&gt;
         +===+&lt;br /&gt;
         |=K=|&lt;br /&gt;
         +===+&lt;br /&gt;
 &lt;br /&gt;
                 +---+---+===+&lt;br /&gt;
                 | Q | S |=U=|&lt;br /&gt;
                 +---+---+===+&lt;br /&gt;
 &lt;br /&gt;
                 +---+===+&lt;br /&gt;
                 | Q |=S=|&lt;br /&gt;
                 +---+===+&lt;br /&gt;
 &lt;br /&gt;
                 +===+&lt;br /&gt;
                 |=Q=|&lt;br /&gt;
                 +===+&lt;br /&gt;
         &lt;br /&gt;
 +---+---+---+---+---+---+---+&lt;br /&gt;
 | C | I | K | O | Q | S | U |  &lt;br /&gt;
 +---+---+---+---+---+---+---+&lt;br /&gt;
&lt;br /&gt;
==== Alternatives Sortieren kleiner Intervalle ====&lt;br /&gt;
&lt;br /&gt;
Für kleine Arrays (bis zu einer gegebenen Größe K) ist das &amp;quot;Teile und herrsche&amp;quot;-Prinzip nicht die effizienteste Herangehensweise. Insbesondere kann man ein Array mit maximal 3 Elementen direkt sortieren:&lt;br /&gt;
 def sortThree(a, l, r):&lt;br /&gt;
     if r &amp;gt; l and a[l+1] &amp;lt; a[l]:          # Stelle sicher, dass a[l] und a[l+1] relativ zueinander sortiert sind.&lt;br /&gt;
         a[l], a[l+1] = a[l+1], a[l]&lt;br /&gt;
     if r == l + 2:&lt;br /&gt;
         if a[r] &amp;lt; a[l]:                  # Stelle sicher, dass a[l] und a[r] relativ zueinander sortiert sind.&lt;br /&gt;
             a[l], a[r] = a[r], a[l]      # Danach ist a[l] auf jeden Fall das kleinste Element.&lt;br /&gt;
         if a[r] &amp;lt; a[r-1]:                # Stelle sicher, dass a[r-1] und a[r] relativ zueinander sortiert sind.&lt;br /&gt;
             a[r], a[r-1] = a[r-1], a[r]  # Jetzt ist a[r] auf jeden Fall das größte Element und das Array damit sortiert.&lt;br /&gt;
&lt;br /&gt;
In die Funktion &amp;lt;tt&amp;gt;quicksort()&amp;lt;/tt&amp;gt; wird jetzt ein Aufruf dieser Funktion eingefügt:&lt;br /&gt;
     if r &amp;gt; l + 2:&lt;br /&gt;
         # wie bisher&lt;br /&gt;
     elif r &amp;gt; l:&lt;br /&gt;
         sortThree(a, l, r)&lt;br /&gt;
&lt;br /&gt;
==== Günstige Selektion des Pivot-Elements ====&lt;br /&gt;
Durch geschickte Wahl des Pivot-Elements kann man erreichen, dass der ungünstigste Fall (quadratische Laufzeit) nur mit sehr kleiner Wahrscheinlichkeit eintritt. Zwei Möglichkeiten haben sich bewährt:&lt;br /&gt;
# Anstatt des letzten Elements des Teilarrays wählt man ein zufälliges Element (mit Hilfe eines Zufallszahlengenerators). Dadurch wird Quick Sort unempfindlich gegenüber bereits sortierten Arrays, weil die Teilung im Mittel wie bei einem zufällig sortierten Array erfolgt (typischer Fall in obiger Laufzeitberechnung).&lt;br /&gt;
# Median (mittlerer Wert) von drei Elementen: Verwende den Median des ersten, mittleren und letzten Elements jedes Teilarrays als Pivot-Element.&lt;br /&gt;
In beiden Fällen ist es praktisch ausgeschlossen, dass ein Eingabearray so angeordnet ist, dass in jedem Teilarray gerade das kleinste oder größte Element als Pivot gewählt wird. Nur dann könnte der ungünstigste Fall jedoch eintreten, was somit effektiv verhindert wird.&lt;/div&gt;</summary>
		<author><name>Jschleic</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Sortieren&amp;diff=2534</id>
		<title>Sortieren</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Sortieren&amp;diff=2534"/>
				<updated>2008-07-22T12:53:47Z</updated>
		
		<summary type="html">&lt;p&gt;Jschleic: /* Mengentheoretische  Anforderungen */  Formeln verschönert&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;----&lt;br /&gt;
== Laufzeitmesung in Python ==&lt;br /&gt;
&lt;br /&gt;
Verwendung der '''timeit-Bibliothek''' für die Hausaufgabe. &lt;br /&gt;
&lt;br /&gt;
* Importiere das timeit-Modul: &amp;lt;tt&amp;gt;import timeit&amp;lt;/tt&amp;gt;&lt;br /&gt;
* Teile den Algorithmus in die Initialisierungen und den Teil, dessen Geschwindigkeit gemessen werden soll. Beide Teile werden in jeweils einen (mehrzeiligen) String eingeschlossen:&lt;br /&gt;
&lt;br /&gt;
  +--------+     +----+            setup = &amp;quot;&amp;quot;&amp;quot;            prog = &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
  |  algo  | --&amp;gt; |init|                +----+                 +----+&lt;br /&gt;
  |        |     +----+                |init|                 |prog|&lt;br /&gt;
  |        |                           +----+                 +----+&lt;br /&gt;
  |        |     +----+             &amp;quot;&amp;quot;&amp;quot;                     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
  |        | --&amp;gt; |prog|            &lt;br /&gt;
  +--------+     +----+            &lt;br /&gt;
&lt;br /&gt;
* aus den beiden Strings wird ein Timeit-Objekt erzeugt: &amp;lt;tt&amp;gt;t = timeit.Timer(prog, setup)&amp;lt;/tt&amp;gt;&lt;br /&gt;
* Frage: Wie oft soll die Algorithmik wiederholt werden&lt;br /&gt;
:z.B. N = 1000&lt;br /&gt;
* Zeit in Sekunden für N Durchläufe: &amp;lt;tt&amp;gt;K = t.timeit(N)&amp;lt;/tt&amp;gt;&lt;br /&gt;
:Zeit für 1 Durchlauf: K/N&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
3.Stunde am 16.04.2008&lt;br /&gt;
&lt;br /&gt;
==Sortierverfahren==&lt;br /&gt;
&lt;br /&gt;
=== Motivation ===&lt;br /&gt;
'''Def:''' &lt;br /&gt;
Ein Sortierverfahren ist ein Algorithmus, der dazu dient, eine Liste von Elementen zu sortieren.&lt;br /&gt;
* Literatur, siehe Sortierverfahren; Bubblesort 1956, Quicksort 1962. Librarysort 2004  &lt;br /&gt;
&lt;br /&gt;
'''Anwendungen'''&lt;br /&gt;
* Sortierte Daten sind häufig Vorbedingungen für Suchverfahren (Speziell für effiziente Suchalgorithmen mit Komplexität &amp;lt;math&amp;gt;\mathcal{O}(log(N))&amp;lt;/math&amp;gt;)&lt;br /&gt;
* Darstellung von Daten gemäß menschlicher Wahrnehmung &lt;br /&gt;
* Aus programmiertechnischer Anwendungssicht hat das Sortierproblem allerdings heute an Relevanz verloren da&lt;br /&gt;
** gängige Programmiersprachen heute typunabhängige Algorithmen zur Verfügung stellen. Der Programmierer braucht sich deshalb in den meisten Fällen nicht mehr um die Implementierung von Sortieralgorithmen zu kümmern. In C/C++ sorgen dafür beispielsweise Methoden aus der [http://de.wikipedia.org/wiki/Standard_Template_Library STL].&lt;br /&gt;
** Festplatten / Hauptspeicher heute weniger limitierenden Charakter haben, so dass Standardsortierverfahren meist ausreichen, während komplizierte, speicher-sparende Sortieralgorithmen nur noch selten benötigt werden.&lt;br /&gt;
* Die Kenntnis grundlegender Sortieralgorithmen ist trotzdem immer noch nötig: Einerseits kann man vorgefertigte Bausteine nur dann optimal einsetzen, wenn man weiß, was hinter den Kulissen passiert und andererseits verdeutlicht gerade das Sortierproblem wichtige Prinzipien der Algorithmenentwicklung und -analyse in sehr anschaulicher Form.&lt;br /&gt;
&lt;br /&gt;
=== Vorraussetzungen/ Spielregeln ===&lt;br /&gt;
&lt;br /&gt;
==== Mengentheoretische  Anforderungen====&lt;br /&gt;
Definition Totale Ordnung/ Total gordnete Menge:&lt;br /&gt;
Eine Totale Ordnung / Total geordnete Menge ist eine binäre Relation    &lt;br /&gt;
&amp;lt;math&amp;gt;R \subseteq M \times M&amp;lt;/math&amp;gt; über einer Menge &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt;, die transitiv, antisymmetrisch und total ist.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; sei  dargestellt als infix Notation &amp;lt;math&amp;gt;\le &amp;lt;/math&amp;gt; dann, falls M total geordnet, gilt &lt;br /&gt;
&amp;lt;math&amp;gt; \forall a,b,c \ \epsilon M &amp;lt;/math&amp;gt; &amp;lt;br/&amp;gt;&lt;br /&gt;
(1) &amp;lt;math&amp;gt;a \le b \wedge b \le a \Rightarrow a=b &amp;lt;/math&amp;gt; (antisymmetrisch)&amp;lt;br/&amp;gt;&lt;br /&gt;
(2) &amp;lt;math&amp;gt;a \le b \wedge b \le c \Rightarrow a \le c &amp;lt;/math&amp;gt; (transitiv)&amp;lt;br/&amp;gt;&lt;br /&gt;
(3) &amp;lt;math&amp;gt;a \le b \vee b \le a &amp;lt;/math&amp;gt; (total) &amp;lt;br/&amp;gt;&lt;br /&gt;
Bemerkung: aus (3) folgt &amp;lt;math&amp;gt; a \le a &amp;lt;/math&amp;gt; (reflexiv) &amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
''Hab in der Wiki eine gute Seite dazu gefunden [http://de.wikipedia.org/wiki/Ordnungsrelation'' Ordnungsrelation]&lt;br /&gt;
&lt;br /&gt;
==== Datenspeicherung ====&lt;br /&gt;
&lt;br /&gt;
Die Daten liegen typischerweise in Form von Arrays oder verketteten Listen vor. Ja nach Datenstruktur sind andere Sortieralgorithmen am besten geeignet. &lt;br /&gt;
;Array:&lt;br /&gt;
        +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
        |///|   |   |   |   |   |   |   |///|  &lt;br /&gt;
        +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
       \________________  ____________________/&lt;br /&gt;
                        \/&lt;br /&gt;
                        N&lt;br /&gt;
Datenelemente können über Indexoperation a[i] gelesen, überschrieben und miteinander vertauscht werden. Vorteil: Die Zugriffsreihenfolge auf die Datenelemente ist beliebig. Nachteil: Einfügen oder Löschen von Elementen aus dem Array ist relativ aufwändig.&lt;br /&gt;
&lt;br /&gt;
;Vekettete Liste:  &lt;br /&gt;
        +---+     +---+     +---+&lt;br /&gt;
        |   | --&amp;gt; |   | --&amp;gt; |   | --&amp;gt; Ende &lt;br /&gt;
        +---+     +---+     +---+&lt;br /&gt;
   &lt;br /&gt;
Jeder Knoten der Liste enthält ein Datenelement und einen Zeiger auf den nächsten Knoten. Vorteil: Einfügen und Löschen von Elementen ist effizient möglich. Nachteil: effizienter Zugriff nur auf den Nachfolger eines gegebenen Elements, d.h. Zugriffsreihenfolge ist nicht beliebig.&lt;br /&gt;
&lt;br /&gt;
==== Stabilität ====&lt;br /&gt;
&lt;br /&gt;
Ein Sortierverfahren heißt ''stabil'' falls die relative Reihenfolge gleicher Schlüssel durch die Sortierung nicht verändert wird.&lt;br /&gt;
&lt;br /&gt;
Beispiel: Sortiere eine Liste von Paaren &amp;lt;tt&amp;gt;[(3,7), (4,2), (4,1), (2,2), (2,8)]&amp;lt;/tt&amp;gt;, wobei die Reihenfolge nur durch das erste Element (Schlüsselelement) jeden Paares festgelegt wird.&lt;br /&gt;
Dann erzeugt ein stabiles Sortierverfahren die Ausgabe&lt;br /&gt;
 [(2,2), (2,8), (3,7), (4,2), (4,1)]&lt;br /&gt;
während die Ausgabe&lt;br /&gt;
 [(2,2), (2,8), (3,7), (4,1), (4,2)]&lt;br /&gt;
nicht stabil ist (die Paare &amp;lt;tt&amp;gt;(4,1), (4,2)&amp;lt;/tt&amp;gt; sind vertauscht).&lt;br /&gt;
&lt;br /&gt;
==== Charakterisierung der Effizienz von Algorithmen ====&lt;br /&gt;
  &lt;br /&gt;
:(a) Komplexität O(   1), O(n), etc. wird in Kapitel [[Effizienz]] erklärt.&lt;br /&gt;
:(b) Zählen der notwendigen Vergleiche&lt;br /&gt;
:(c) Messen der Laufzeit mit 'timeit' (auf identischen Daten)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Rekursive Beziehungen'''&lt;br /&gt;
zerlegt die ursprünglichen Probleme in kleinere Probleme und wendet den Algorithmus auf die kleineren Probleme an; daraufhin werden die Teilprobleme zur Lösung des Gesamtproblems verwendet. &lt;br /&gt;
d.h. Laufzeit (operativer Vergleich) für N Eingaben hängt von der Laufzeit der Eingaben für die Teilprobleme &lt;br /&gt;
&lt;br /&gt;
'''Aufwand'''&lt;br /&gt;
&lt;br /&gt;
(i) rekursives/ lineares Durchlaufen der Eingabedaten, Bearbeitung einzelner Elemente&lt;br /&gt;
&lt;br /&gt;
 C(N)= C(N-1)+ N ;  N&amp;gt;1, C(1)= 1             +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
     = C(N-2) +(N-1)+ N                      | 7  | 3 | 2 | 5 | 6 | 8 | 1 | 4 | 2 |  &lt;br /&gt;
     = C(N-3) + (N-2) + (N-1) + N            +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
     = ...                                         ________________________/&lt;br /&gt;
     = C(1) + 2+...+(N-1) +N                     /&lt;br /&gt;
                                               +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
        N(N+1)   N²                            | 1 | 3 | 2 | 5 | 6 | 8 | 7 | 4 | 2 |  &lt;br /&gt;
      = -----  ~ --                            +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
          2       2                   &lt;br /&gt;
            &lt;br /&gt;
                                                      &lt;br /&gt;
                                               &lt;br /&gt;
   &lt;br /&gt;
&lt;br /&gt;
(ii) rekursives halbieren der Menge der Eingabedaten&lt;br /&gt;
    &lt;br /&gt;
 C(N)= C(N/2)+1 ; N&amp;gt;1, C(1)=0&lt;br /&gt;
 Aus Gründen der Einfachheit sei N  = 2n&lt;br /&gt;
 &lt;br /&gt;
 C(N)= C(2^n)= C(&amp;lt;math&amp;gt;2^{n-1}&amp;lt;/math&amp;gt;) + 1       &lt;br /&gt;
                     &lt;br /&gt;
              = C(&amp;lt;math&amp;gt;2^{n-1}&amp;lt;/math&amp;gt;) + 1 + 1   &lt;br /&gt;
              = ...                    &lt;br /&gt;
                                       &lt;br /&gt;
              = C(&amp;lt;math&amp;gt;2^0&amp;lt;/math&amp;gt;) + n               &lt;br /&gt;
              = n                    &lt;br /&gt;
              = &amp;lt;math&amp;gt;log_2 N&amp;lt;/math&amp;gt;                 &lt;br /&gt;
 +---+---+---+---+-|-+---+---+---+---+ &lt;br /&gt;
 |   |    |   |   |   |   |    |   |   |&lt;br /&gt;
 +---+---+---+---+-|-+---+---+---+---+&lt;br /&gt;
 +---+---+---+---+&lt;br /&gt;
 |   |    |   |   |&lt;br /&gt;
 +---+---+---+---+&lt;br /&gt;
 +---+---+        +---+&lt;br /&gt;
 |   |    |  -&amp;gt;   |   |&lt;br /&gt;
 +---+---+        +---+&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
(iii) rekursives halbieren, lineare Bearbeitung, jedes Elements&lt;br /&gt;
  &lt;br /&gt;
 C(N)= 2C(N/2)+ N; N&amp;gt;1, C(1)= 0&lt;br /&gt;
 Sei N= &amp;lt;math&amp;gt;2^n&amp;lt;/math&amp;gt;&lt;br /&gt;
 C(N)= C(&amp;lt;math&amp;gt;2^n&amp;lt;/math&amp;gt;)= 2C (&amp;lt;math&amp;gt;2^{n-1}&amp;lt;/math&amp;gt;)+ &amp;lt;math&amp;gt;2^n&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;=&amp;gt; &amp;lt;math&amp;gt; \cfrac{C(2^n)}{2^n}&amp;lt;/math&amp;gt; = &amp;lt;math&amp;gt; \cfrac{2C(2^{n-1})}{2^{n-1}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 = &amp;lt;math&amp;gt; \cfrac{2C(2^{n-2})+2^{n-1}}{2^{n-1}}+1&amp;lt;/math&amp;gt;&lt;br /&gt;
 = &amp;lt;math&amp;gt; \cfrac{2C(2^{n-2})}{2^{n-2}}+1 +1&amp;lt;/math&amp;gt;&lt;br /&gt;
 =...&lt;br /&gt;
 = n&lt;br /&gt;
&amp;lt;=&amp;gt; C(&amp;lt;math&amp;gt;2^n&amp;lt;/math&amp;gt;)= &amp;lt;math&amp;gt;2^n&amp;lt;/math&amp;gt; * n&lt;br /&gt;
&amp;lt;=&amp;gt; C= N log&amp;lt;math&amp;gt;_2&amp;lt;/math&amp;gt;N&lt;br /&gt;
&lt;br /&gt;
==Selection Sort==&lt;br /&gt;
&lt;br /&gt;
===Algorithmus===&lt;br /&gt;
&lt;br /&gt;
 array = [...]  # zu sortierendes Array&lt;br /&gt;
 &lt;br /&gt;
 for i in range(len(array)-1):&lt;br /&gt;
    min = i&lt;br /&gt;
    for j in range(i+1, len(array)):&lt;br /&gt;
       if a[j]&amp;lt; a[min]:&lt;br /&gt;
           min = j&lt;br /&gt;
    a[i], a[min] = a[min], a[i]  # Vertausche a[i] mit dem kleinsten rechts befindlichen Element&lt;br /&gt;
                                 # Elemente links von a[i] und a[i] selbst befinden sich nun in ihrer endgültigen Position&lt;br /&gt;
&lt;br /&gt;
Beispiel: Sortieren der Liste &amp;lt;tt&amp;gt;[S,O,R,T,I,N,G]&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
 erste Iteration der äußeren Schleife, Zustand ''vor'' dem Vertauschen:&lt;br /&gt;
  i=0                     min&lt;br /&gt;
 +---+---+---+---+---+---+---+&lt;br /&gt;
 | S | O | R | T | I | N | G |&lt;br /&gt;
 +---+---+---+---+---+---+---+&lt;br /&gt;
 &lt;br /&gt;
 erste Iteration der äußeren Schleife, Zustand ''nach'' dem Vertauschen:&lt;br /&gt;
 +---|---+---+---+---+---+---+&lt;br /&gt;
 | G | O | R | T | I | N | S |&lt;br /&gt;
 +---|---+---+---+---+---+---+&lt;br /&gt;
 &lt;br /&gt;
 zweite Iteration der äußeren Schleife:&lt;br /&gt;
      i=1         min&lt;br /&gt;
 +---|---+---+---+---+---+---+&lt;br /&gt;
 | G | O | R | T | I | N | S |&lt;br /&gt;
 +---|---+---+---+---+---+---+&lt;br /&gt;
 &lt;br /&gt;
 weitere Iterationen:&lt;br /&gt;
          i=2         min&lt;br /&gt;
 +---+---|---+---+---+---+---+&lt;br /&gt;
 | G | I | R | T | O | N | S |&lt;br /&gt;
 +---+---|---+---+---+---+---+ &lt;br /&gt;
 &lt;br /&gt;
              i=3 min&lt;br /&gt;
 +---+---+---|---+---+---+---+&lt;br /&gt;
 | G | I | N | T | O | R | S |&lt;br /&gt;
 +---+---+---|---+---+---+---+ &lt;br /&gt;
 &lt;br /&gt;
                  i=4 min&lt;br /&gt;
 +---+---+---+---+---+---+---+&lt;br /&gt;
 | G | I | N | O | T | R | S |&lt;br /&gt;
 +---+---+---+---+---+---+---+  &lt;br /&gt;
 ...&lt;br /&gt;
&lt;br /&gt;
===Laufzeit===&lt;br /&gt;
&lt;br /&gt;
Da in jeder Iteration der ''inneren'' Schleife ein Vergleich &amp;lt;tt&amp;gt;a[j]&amp;lt; a[min]&amp;lt;/tt&amp;gt; durchgeführt wird, ist die Anzahl der Vergleiche ein gutes Maß für den Aufwand des Algorithmus und damit für die Laufzeit. Sei C(N) die Anzahl der notwendigen Vergleiche, um ein Array der Größe N zu sortieren. Die Arbeitsweise des Algorithmus kann dann so beschrieben werden: Führe N-1 Vergleiche aus, bringe das kleinste Element an die erste Stelle, und fahre mit dem Sortieren des Rest-Arrays (Größe N-1) rechts des ersten Elements fort. Dafür sind nach Definition noch C(N-1) Vergleiche nötig. Es gilt also:&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = C(N-1) + (N-1)&amp;lt;/math&amp;gt;&lt;br /&gt;
C(N-1) können wir nach der gleichen Formel einsetzen, und erhalten:&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = C(N-2) + (N-2) + (N-1)&amp;lt;/math&amp;gt;&lt;br /&gt;
Wir können in dieser Weise weiter fortfahren. Bei C(1) wird das Einsetzen beendet, denn für ein Array der Länge 1 sind keine Vergleiche mehr nötig, also C(1) = 0. Wir erhalten somit&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = C(N-3) + (N-3) + (N-2) + (N-1)&amp;lt;/math&amp;gt;&lt;br /&gt;
:::&amp;lt;math&amp;gt;...&amp;lt;/math&amp;gt;&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = C(1) + 1 + 2 + ...+ (N-2)+ (N-1)&amp;lt;/math&amp;gt;&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = 0 + 1 + 2 + ...+ (N-2)+ (N-1)&amp;lt;/math&amp;gt;&lt;br /&gt;
Nach der Gaußschen Summenformel ist dies&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = \frac {(N-1)N}{2}\approx \cfrac {(N^2)}{2}&amp;lt;/math&amp;gt; (für große N).&lt;br /&gt;
&lt;br /&gt;
In jedem Durchlauf der äußeren Schleife werden außerdem zwei Elemente ausgetauscht. Es gilt für die Anzahl der Austauschoperationen&lt;br /&gt;
:::&amp;lt;math&amp;gt;A(N)= N-1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Stabilität===&lt;br /&gt;
&lt;br /&gt;
Selection Sort ist stabil, wenn die Vergleiche durch &amp;lt;tt&amp;gt;a[j] &amp;lt; a[min]&amp;lt;/tt&amp;gt; erfolgen, weil dann immer das erste Element mit einem gegebenen Schlüssel als erster nach vorn gebracht wird. Bei Vergleichen &amp;lt;tt&amp;gt;a[j] &amp;lt;= a[min]&amp;lt;/tt&amp;gt; wird hingegen das letzte Element zuerst nach vorn gebracht, somit ist Selection Sort dann nicht stabil.&lt;br /&gt;
&lt;br /&gt;
==Insertion Sort==&lt;br /&gt;
&lt;br /&gt;
* wird in der Übungsgruppe behandelt, siehe auch in der [http://de.wikipedia.org/wiki/Insertionsort WikiPedia]&lt;br /&gt;
* Erweiterung: [http://en.wikipedia.org/wiki/Shell_sort Shell sort]&lt;br /&gt;
&lt;br /&gt;
== Mergesort ==&lt;br /&gt;
&lt;br /&gt;
===Algorithmus ===&lt;br /&gt;
&lt;br /&gt;
Zugrunde liegende Idee: &lt;br /&gt;
* Zerlege das Problem in zwei möglichst gleich große Teilprobleme (&amp;quot;Teile und herrsche&amp;quot;-Prinzip -- divide and conquer)&lt;br /&gt;
* Löse die Teilprobleme rekursiv&lt;br /&gt;
* Führe die Teillösungen über Mischen (merging) in richtig sortierter Weise zusammen.&lt;br /&gt;
Der Algorithmus besteht somit aus zwei Teilen&lt;br /&gt;
&lt;br /&gt;
====Zusammenführen -- merge====&lt;br /&gt;
&lt;br /&gt;
a und b sind zwei sortierte Listen, die in eine sortierte Ergebnisliste kombiniert werden.&lt;br /&gt;
&lt;br /&gt;
 def merge(a,b):&lt;br /&gt;
     c = []   # zunächst leere Ergebnisliste &lt;br /&gt;
     i, j = 0, 0&lt;br /&gt;
     while i &amp;lt; len(a) and j &amp;lt; len(b):&lt;br /&gt;
         # wähle des kleinste der noch nicht angefügten Elemente&lt;br /&gt;
         if a[i] &amp;lt;= b[j]:&lt;br /&gt;
              c.append(a[i])&lt;br /&gt;
              i += 1&lt;br /&gt;
         else:&lt;br /&gt;
              c.append(b[j])&lt;br /&gt;
              j += 1&lt;br /&gt;
    # eine Liste ist jetzt aufgebraucht =&amp;gt; der Rest der anderen wird einfach an c angehängt&lt;br /&gt;
    if i &amp;lt; len(a):&lt;br /&gt;
        c += a[i:]&lt;br /&gt;
    else:&lt;br /&gt;
        c += b[j:]&lt;br /&gt;
    return c&lt;br /&gt;
&lt;br /&gt;
====rekursives Sortieren====&lt;br /&gt;
&lt;br /&gt;
 def mergeSort(a):  # a ist das zu sortierende Array&lt;br /&gt;
     if len(a) &amp;lt;= 1:&lt;br /&gt;
         return a   # Rekursionsabschluß: leere Arrays und Arrays mit einem Element müssen nicht sortiert werden&lt;br /&gt;
     else:&lt;br /&gt;
         left  = a[:len(a)/2]   # linkes Teilarray&lt;br /&gt;
         right = a[len(a)/2:]   # rechtes Teilarray&lt;br /&gt;
         leftSorted  = mergeSort(left)  # rekursives Sortieren der Teilarrays&lt;br /&gt;
         rightSorted = mergeSort(right) # ...&lt;br /&gt;
         return merge(leftSorted, rightSorted)  # Zusammenführen der Teilarrays&lt;br /&gt;
&lt;br /&gt;
Bei der Sortierung mit Mergesort wird das Array immer in zwei Teile geteilt. → Es entsteht ein Binärbaum der Tiefe &amp;lt;math&amp;gt;\log_2 N&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Beispiel: Sortieren der Liste &amp;lt;tt&amp;gt;[S,O,R,T,I,N,G]&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Der Algorithmus läuft in der folgenden Skizze zunächst rekursiv von unten nach oben (Zerlegen in Teillisten), danach werden die sortierten Teillisten von oben nach unten zusammengeführt (diese sortierten Teillisten sind in der Skizze dargestellt).&lt;br /&gt;
&lt;br /&gt;
 Schritt 0:&lt;br /&gt;
  S 0 R T I N G     S    O R    T I    N     G    #Arraylänge: N/8    Vergleiche: 0&lt;br /&gt;
 Schritt 1:          \  /   \  /   \  /     /&lt;br /&gt;
  OS RT IN G          OS     RT     IN     /      #Arraylänge: N/4    Vergleiche: 3 * 2 = 6&lt;br /&gt;
 Schritt 2:             \    /        \   /       &lt;br /&gt;
  ORST GIN               ORST          GIN        #Arraylänge: N/2    Vergleiche: 4 + 3 = 7&lt;br /&gt;
                            \         /&lt;br /&gt;
 Schritt3:                   \       /&lt;br /&gt;
  GINORST                     GINORST             #Arraylänge: N      Vergleiche: N     = 7&lt;br /&gt;
&lt;br /&gt;
===Laufzeit ===&lt;br /&gt;
&lt;br /&gt;
Man erkennt an der Skizze, dass der Rekursionsbaum für ein Array der Länge N die Tiefe log N hat. Auf jeder Ebene werden weniger als N Vergleiche ausgeführt, so dass insgesamt weniger als N*log N Vergleiche benötigt werden. Dies ist natürlich wesentlich effizienter als die (N-1)*N/2 Vergleiche von Selection Sort. Mathematisch exakt kann man die Anzahl der Vergleiche durch die folgende Rekursionsformel berechnen:&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = C(\lfloor N/2\rfloor) + C(\lceil N/2\rceil) + N&amp;lt;/math&amp;gt;&lt;br /&gt;
Der Aufwand ergibt sich aus dem Aufwand für die beiden Teilprobleme plus dem Aufwand für N Vergleiche beim Zusammenführen der sortierten Teillisten. Dabei stehen die Zeichen &amp;lt;math&amp;gt;\lfloor \rfloor&amp;lt;/math&amp;gt; und &amp;lt;math&amp;gt;\lceil \rceil&amp;lt;/math&amp;gt; für abrunden bzw. aufrunden, weil ein Problem mit ungeradem N nicht in zwei exakt gkeiche Teile geteilt werden kann. Um diese Komplikation zu vermeiden, beschränken wir uns im folgenden auf den Fall &amp;lt;math&amp;gt;N = 2^n&amp;lt;/math&amp;gt; (mit etwas höherem Aufwand kann man zeigen, dass diese Einschränkung nicht notwendig ist und die Resultate für alle N gelten). Die vereinfachte Aufwandsformel lautet:&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = 2 C(N/2) + N&amp;lt;/math&amp;gt;&lt;br /&gt;
Durch Einsetzen der Formel für N/2 erhalten wir:&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = 2 (2 C(N/4) + N/2) + N = 4 C(N/4) + N + N&amp;lt;/math&amp;gt;&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = 4 (2 C(N/8) + N/4) + N + N = 8 C(N/8) + N + N + N&amp;lt;/math&amp;gt;&lt;br /&gt;
:::&amp;lt;math&amp;gt;...&amp;lt;/math&amp;gt;&lt;br /&gt;
Die Rekursion endet, weil für ein Array der Größe &amp;lt;math&amp;gt;N=1&amp;lt;/math&amp;gt; keine Vergleiche mehr benötigt werden, also &amp;lt;math&amp;gt;C(1) = 0&amp;lt;/math&amp;gt; gilt. Mit &amp;lt;math&amp;gt;N=2^n&amp;lt;/math&amp;gt; ist dies aber gerade nach &amp;lt;math&amp;gt;n = \log_2 N&amp;lt;/math&amp;gt; Zerlegungen der Fall. Merge Sort benötigt also&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = N + ... + N = n \cdot N = N\cdot \log_2 N&amp;lt;/math&amp;gt;&lt;br /&gt;
Vergleiche.&lt;br /&gt;
&lt;br /&gt;
===Weitere Eigenschaften von MergeSort ===&lt;br /&gt;
&lt;br /&gt;
* Mergesort ist '''stabil''': wegen des Vergleichs &amp;lt;tt&amp;gt;a[i] &amp;lt;= b[j]&amp;lt;/tt&amp;gt; wird die Position gleicher Schlüssel im Algorithmus &amp;lt;tt&amp;gt;merge(a,b)&amp;lt;/tt&amp;gt; nicht verändert -- bei gleichem Schlüssel hat, wie gefordert, das linke Element Vorrang.&lt;br /&gt;
* Mergesort ist '''unempfindlich gegenüber der ursprünglichen Reihenfolge der Eingabedaten'''. Grund dafür ist&lt;br /&gt;
** die vollständige Aufteilung des Ausgangsarrays in Arrays der Länge 1 und&lt;br /&gt;
** dass &amp;lt;tt&amp;gt;merge(a,b)&amp;lt;/tt&amp;gt; die Vorsortierung nicht ausnutzt, d.h. die Komplexität von &amp;lt;tt&amp;gt;merge(a,b)&amp;lt;/tt&amp;gt; ist sortierungsunabhängig.&lt;br /&gt;
* Diese Eigenschaft kann unerwünscht sein, wenn ein Teil des Arrays oder gar das ganze Array schon sortiert ist. Es wird nämlich in jedem Fall das ganze Array neu sortiert.&lt;br /&gt;
* Merge Sort eignet sich für das Sortieren von '''verketteten Listen''', weil die Listenelemente stets von vorn nach hinten durchlaufen werden. In diesem Fall muss &amp;lt;tt&amp;gt;merge(a, b)&amp;lt;/tt&amp;gt; keine neue Liste &amp;lt;tt&amp;gt;c&amp;lt;/tt&amp;gt; für das Ergebnis anlegen, sondern kann einfach die Verkettung der Listenelemente von &amp;lt;tt&amp;gt;a&amp;lt;/tt&amp;gt; und &amp;lt;tt&amp;gt;b&amp;lt;/tt&amp;gt; entsprechend anpassen. In diesem Sinne arbeitet Merge Sort auf verketten Listen &amp;quot;in place&amp;quot;, d.h. es wird kein zusätzlicher Speicher benötigt.&lt;br /&gt;
* Im Gegensatz dazu benötigt &amp;lt;tt&amp;gt;merge(a,b)&amp;lt;/tt&amp;gt; zusätzlichen Speicher für das Ergebnis &amp;lt;tt&amp;gt;c&amp;lt;/tt&amp;gt;, wenn die Daten in einem Array gegeben sind.&lt;br /&gt;
&lt;br /&gt;
== Quicksort ==&lt;br /&gt;
&lt;br /&gt;
* Quicksort wurde in den 60er Jahren von Charles Antony Richard Hoare [http://de.wikipedia.org/wiki/C._A._R._Hoare] entwickelt. Es gibt viele Implementierungen von Quicksort, vgl. [http://de.wikipedia.org/wiki/Quicksort].&lt;br /&gt;
* Dieser Algorithmus gehört zu den &amp;quot;Teile und herrsche&amp;quot;-Algorithmen (divide-and-conquer) und ist der Standardalgorithmus für Sortieren.&lt;br /&gt;
* Im Gegensatz zu Merge Sort wird das Problem aber nicht immer in zwei fast gleich große Teilprobleme zerlegt. Dadurch vermeidet man, dass zusätzlicher Speicher benötigt wird (Quick Sort arbeitet auch für Arrays &amp;quot;in place&amp;quot;). Allerdings erkauft man sich dies dadurch, dass Quick Sort bei ungünstigen Eingaben (die Bedeutung von &amp;quot;ungünstig&amp;quot; ist je nach Implementation verschieden) nicht effizient arbeitet. Da solche Eingaben jedoch in der Praxis fast nie vorkommen, tut dies der Beliebtheit von Quicksort keinen Abbruch.&lt;br /&gt;
&lt;br /&gt;
=== Algorithmus===&lt;br /&gt;
&lt;br /&gt;
Wie Merge Sort arbeitet Quick Sort rekursiv. Hier werden die Daten allerdings zuerst vorbereitet (in der Funktion &amp;lt;tt&amp;gt;partition&amp;lt;/tt&amp;gt;), und danach erfolgt der rekursive Aufruf:&lt;br /&gt;
&lt;br /&gt;
 def quicksort(a, l, r): &lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;a ist das zu sortierende Array, &lt;br /&gt;
        l und r sind die linke und rechte Grenze des zu sortierenden Bereichs&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
      if r &amp;gt; l:                     # Rekursionsabschluss: wenn r &amp;lt;= l, ist der Bereich leer und muss nicht mehr sortiert werden&lt;br /&gt;
          i = partition(a, l, r)    # i ist der Index des sog. Pivot-Elements (s. u.)&lt;br /&gt;
          quicksort(a, l, i-1)      # rekursives Sortieren der beiden Teilarrays&lt;br /&gt;
          quicksort(a, i+1, r)      # ...&lt;br /&gt;
&lt;br /&gt;
Der Schlüssel des Algorithmus ist offensichtlich die Funktion &amp;lt;tt&amp;gt;partition&amp;lt;/tt&amp;gt;. Diese wählt ein Element des Arrays aus (das Pivot-Element) und bringt es an die richtige Stelle (also an den Index &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt;, der von &amp;lt;tt&amp;gt;partition&amp;lt;/tt&amp;gt; zurückgegeben wird). Ausserdem stellt sie sicher, dass alle Elemente in der linken Teilliste (Index &amp;lt; &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt;) kleiner als &amp;lt;tt&amp;gt;a[i]&amp;lt;/tt&amp;gt;, und alle Elemente in der rechten Teilliste größer also &amp;lt;tt&amp;gt;a[i]&amp;lt;/tt&amp;gt; sind:&lt;br /&gt;
# &amp;lt;math&amp;gt;a[i]&amp;lt;/math&amp;gt; ist sortiert, d.h. dieses Element ist am endgültigen Platz.&lt;br /&gt;
# &amp;lt;math&amp;gt;\forall x \in \left\{ a \left[ l \right] , ... a \left[ i-1 \right] \right\} : x \leq a \left[ i \right]&amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt;\forall x \in \left\{ a \left[ i+1 \right], ... a \left[ r \right] \right\} : x \geq a \left[ i \right]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
          l                               r&lt;br /&gt;
        +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 Array: |   |   |   |   |\\\|   |   |   |   |&lt;br /&gt;
        +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
         \______  _____/  i  \______  _____/ &lt;br /&gt;
                \/                  \/&lt;br /&gt;
             &amp;lt;=a[i]               &amp;gt;=a[i]             (a[i] ist das Pivot-Element)&lt;br /&gt;
&lt;br /&gt;
Die Position von &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt; richtet sich also offensichtlich danach, wie viele Elemente im Bereich &amp;lt;tt&amp;gt;l&amp;lt;/tt&amp;gt; bis &amp;lt;tt&amp;gt;r&amp;lt;/tt&amp;gt; kleiner bzw. größer als das gewählte Pivot-Element sind. Der Wahl eines guten Pivot-Elements kommt demnach eine große Bedeutung zu (s.u.). &lt;br /&gt;
&lt;br /&gt;
In der einfachsten Version wird &amp;lt;tt&amp;gt;partition&amp;lt;/tt&amp;gt; wie folgt definiert:&lt;br /&gt;
&lt;br /&gt;
 def partition(a, l, r):&lt;br /&gt;
     pivot = a[r]     # Pivot-Element. Hier wird willkürlich das letzte Element verwendet.&lt;br /&gt;
     i  = l           # i und j sind Laufvariablen&lt;br /&gt;
     j  = r - 1&lt;br /&gt;
 &lt;br /&gt;
     while True:&lt;br /&gt;
         while a[i] &amp;lt;= pivot and i &amp;lt; r:&lt;br /&gt;
             i += 1               # finde von links das erste Element &amp;gt; pivot&lt;br /&gt;
         while a[j] &amp;gt;= pivot and j &amp;gt; l:&lt;br /&gt;
             j -= 1               # finde von rechts den ersten Eintrag &amp;lt;= pivot&lt;br /&gt;
         if i &amp;gt;= j: break         # keine weiteren Elemente zum Tauschen =&amp;gt; Schleife beenden      &lt;br /&gt;
         a[i], a[j] = a[j], a[i]  # a[i] und a[j] sind beide auf der falschen Seite des Pivot =&amp;gt; vertausche sie&lt;br /&gt;
     if a[i] &amp;gt; pivot:&lt;br /&gt;
         a[i], a[r] = a[r], a[i]&lt;br /&gt;
     return i&lt;br /&gt;
&lt;br /&gt;
Die folgende Skizze verdeutlicht das Austauschen&lt;br /&gt;
&lt;br /&gt;
                                          p&lt;br /&gt;
        +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 Array: |   |   |   |   |   |   |   |   |\\\|&lt;br /&gt;
        +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
        ------&amp;gt; a[i]&amp;gt;p          a[j]&amp;lt;p &amp;lt;-----&lt;br /&gt;
                  |               |&lt;br /&gt;
                  +---------------+&lt;br /&gt;
       Diese zwei Elemente werden ausgetauscht.&lt;br /&gt;
 &lt;br /&gt;
Dies wird wiederholt, bis sich die Zeiger treffen oder einander überholt haben. Am Schluss wird das Pivot-Element an die richtige Stelle verschoben:&lt;br /&gt;
 &lt;br /&gt;
                          p&lt;br /&gt;
        +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 Array: |   |   |   |   |\\\|   |   |   |   |&lt;br /&gt;
        +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
                          i&lt;br /&gt;
        -----------------&amp;gt; &amp;lt;-----------------&lt;br /&gt;
  &lt;br /&gt;
Beispiel: Partitionieren des Arrays &amp;lt;tt&amp;gt;[A,S,O,R,T,I,N,G,E,X,A,M,P,L,E]&amp;lt;/tt&amp;gt; mit Pivot 'E'.&lt;br /&gt;
&lt;br /&gt;
  l,i --&amp;gt;                                          &amp;lt;-- j   r&lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 | A | S | O | R | T | I | N | G | E | X | A | M | P | L | E |&lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 &lt;br /&gt;
       i &amp;lt;--------- Vertauschen ---------&amp;gt; j               r&lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 | A | S | O | R | T | I | N | G | E | X | A | M | P | L | E |&lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 &lt;br /&gt;
           i &amp;lt;-------------------&amp;gt; j                       r&lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 | A | A | O | R | T | I | N | G | E | X | S | M | P | L | E |&lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 &lt;br /&gt;
           j   i                                           r&lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 | A | A | E | R | T | I | N | G | O | X | S | M | P | L | E |   --&amp;gt; Hier wird die &lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+       Schleife verlassen.&lt;br /&gt;
 &lt;br /&gt;
           j   i &amp;lt;---------------------------------------&amp;gt; r&lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 | A | A | E | R | T | I | N | G | O | X | S | M | P | L | E |   &lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 &lt;br /&gt;
               i                                           r&lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 | A | A | E | E | T | I | N | G | O | X | S | M | P | L | R |   --&amp;gt; Hier wird partition() beendet.&lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Weitere ausführliche Erklärungen der Implementation findet man bei Sedgewick.&lt;br /&gt;
&lt;br /&gt;
=== Laufzeit===&lt;br /&gt;
&lt;br /&gt;
Wir müssen hier den schlechtesten und den typischen Fall unterscheiden. Der schlechteste Fall tritt ein, wenn das Array bereits sortiert ist. Dann ist das Pivot-Element immer bereits am richtigen Platz, so dass &amp;lt;tt&amp;gt;partition(a, l, r)&amp;lt;/tt&amp;gt; stets den Index &amp;lt;tt&amp;gt;i = r&amp;lt;/tt&amp;gt; zurück. Daher wird das Array niemals in zwei etwa gleichgroße Teile zerlegt. Die Anzahl der Vergleiche ergibt sich als&lt;br /&gt;
&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = (N+1) + C(N-1) + C(0)&amp;lt;/math&amp;gt;&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(0) = 0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
mit (N+1) Vergleichen in &amp;lt;tt&amp;gt;partition()&amp;lt;/tt&amp;gt;. Durch sukzessives Einsetzen erhalten wir:&lt;br /&gt;
&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = (N+1) + (N) + (N-1) + ... + 1 = (N+1) N / 2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In diesem Fall ist Quick Sort also nicht schneller als Selection Sort. Wir beschreiben mögliche Verbesserungen unten. Im typischen Fall (wenn nämlich das Array zufällig sortiert ist) sieht die Situation wesentlich besser aus. Bei zufälliger Sortierung wird jeder Index mit gleicher Wahrscheinlichkeit zur Pivot-Position. Wir mitteln deshalb über alle möglichen Positionen:&lt;br /&gt;
&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = (N+1) + \frac{1}{N} \sum_{k=1}^{N} \left[ C(k-1) + C(N-k) \right]&amp;lt;/math&amp;gt; für &amp;lt;math&amp;gt; N&amp;gt;0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
wobei  &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; über alle möglichen Teilungspunkte läuft. Die Summe (der mittlere Aufwand über alle möglichen Zerlegungen) kann vereinfacht werden zu&lt;br /&gt;
:::&amp;lt;math&amp;gt;\frac{1}{N} \sum_{k=1}^{N} \left[ C(k-1) + C(N-k) \right] = 2 \frac{1}{N} \sum_{k=1}^{N} C(k-1) &amp;lt;/math&amp;gt;&lt;br /&gt;
Die Auflösung der Formel ist etwas trickreich. Wir multiplizieren zunächst beide Seiten mit N:&lt;br /&gt;
&lt;br /&gt;
:::&amp;lt;math&amp;gt;&lt;br /&gt;
N \cdot C(N) = N \left[ (N+1) + \frac{2}{N} \sum_{k=1}^{N} C(k-1) \right] = N (N+1) + 2\; \sum_{k=1}^{N} C(k-1)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durch die Substitution &amp;lt;math&amp;gt;N \rightarrow N-1&amp;lt;/math&amp;gt; erhalten wir die entsprechende Formel für N-1:&lt;br /&gt;
&lt;br /&gt;
:::&amp;lt;math&amp;gt;&lt;br /&gt;
(N-1) \cdot C(N-1) = (N-1) N + 2\; \sum_{k=1}^{N-1} C(k-1)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Wir subtrahieren die Formel für N-1 von der Formel für N und eliminieren dadurch die Summe (nur der letzte Summend der ersten Summe bleibt übrig):&lt;br /&gt;
:::&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{rcl}&lt;br /&gt;
N \cdot C(N) - (N-1) \cdot C(N-1) &amp;amp;=&amp;amp; N(N+1) + 2\;\sum_{k=1}^{N} C(k-1)  - (N-1) N - 2\;\sum_{k=1}^{N-1} C(k-1)\\&lt;br /&gt;
&amp;amp;&amp;amp;\\&lt;br /&gt;
N \cdot C(N) - (N-1) \cdot C(N-1) &amp;amp;=&amp;amp; N(N+1) - (N-1) N + 2 C(N-1)&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
Durch Vereinfachen erhalten wir die rekurrente Beziehung&lt;br /&gt;
:::&amp;lt;math&amp;gt;&lt;br /&gt;
N \cdot C(N) = (N+1)\cdot C(N-1) + 2 N&amp;lt;/math&amp;gt;&lt;br /&gt;
Wir teilen jetzt beide Seiten durch &amp;lt;math&amp;gt;(N+1)N&amp;lt;/math&amp;gt;&lt;br /&gt;
:::&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{C(N)}{N+1} = \frac{C(N-1)}{N} + \frac{2}{N+1} &amp;lt;/math&amp;gt;&lt;br /&gt;
Sukzessives Einsetzen der Formel für &amp;lt;math&amp;gt; C(N-1), C(N-2) &amp;lt;/math&amp;gt; etc. bis &amp;lt;math&amp;gt;C(1)=0&amp;lt;/math&amp;gt; liefert&lt;br /&gt;
:::&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{C(N)}{N+1} = \frac{C(N-2)}{N-1} + \frac{2}{N} + \frac{2}{N+1} = \frac{C(2)}{3} + \sum_{k=3}^N\frac{2}{k+1} &amp;lt;/math&amp;gt;&lt;br /&gt;
Für hinreichend große N kann die Summe sehr genau durch ein Integral approximiert werden. Der konstanten Term kann vernachlässigt werden:&lt;br /&gt;
:::&amp;lt;math&amp;gt; &lt;br /&gt;
\frac{C(N)}{N+1} \approx 2 \sum_{k=3}^{N} \frac{1}{k+1} \approx 2 \int_1^N \frac{1}{k} dk = 2 \cdot \ln(N)&amp;lt;/math&amp;gt;&lt;br /&gt;
Somit benötigt Quick Sort im typischen Fall&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N)\approx 2 N\cdot\ln(N) \approx 1.38 N\cdot\log_2(N)&amp;lt;/math&amp;gt;&lt;br /&gt;
Vergleiche. Quick Sort ist demnach etwa genauso schnell wie Merge Sort (in der Praxis sogar etwas schneller, da die innere Schleife von Quick Sort etwas einfacher ist).&lt;br /&gt;
&lt;br /&gt;
=== Verbesserungen des Quicksort-Algorithmus ===&lt;br /&gt;
&lt;br /&gt;
==== Beseitigung der Rekursion ====&lt;br /&gt;
Eine Verbesserung beseitigt die Rekursion durch Verwendung eines Stacks. Nach jeder Partitionierung wird das größere Teilintervall auf dem Stack abgelegt und das kleinere Teilintervall direkt weiterverarbeitet (hierdurch wird sichergestellt, dass die maximale Größe des Stacks minimiert wird).&lt;br /&gt;
&lt;br /&gt;
 def quicksortNonRecursive(a, l, r):&lt;br /&gt;
     stack = [(l,r)]  # initialisiere den Stack&lt;br /&gt;
     while len(stack) &amp;gt; 0:&lt;br /&gt;
         if r &amp;gt; l:&lt;br /&gt;
             i = partition(a, l, r)&lt;br /&gt;
             if (i-l) &amp;gt; (r-i):&lt;br /&gt;
                 stack.append((l,i-1))&lt;br /&gt;
                 l = i+1&lt;br /&gt;
             else:&lt;br /&gt;
                 stack.append((i+1, r))&lt;br /&gt;
                 r = i-1&lt;br /&gt;
         else:&lt;br /&gt;
             l, r = stack.pop()&lt;br /&gt;
&lt;br /&gt;
Die ist die Methode der ''Endrekursionsbeseitigung'', die wir im Kapitel [[Iteration versus Rekursion]] ausführlich behandeln. Die folgende Skizze verdeutlicht die Verwendung des Stacks.&lt;br /&gt;
&lt;br /&gt;
 +---+---+---+---+---+---+---+&lt;br /&gt;
 | Q | U | I | C | K | S | O |&lt;br /&gt;
 +---+---+---+---+---+---+---+&lt;br /&gt;
 &lt;br /&gt;
              &lt;br /&gt;
 +---+---+---+===+---+---+---+&lt;br /&gt;
 | K | C | I |=O=| Q | S | U |&lt;br /&gt;
 +---+---+---+===+---+---+---+&lt;br /&gt;
                  \_________/&lt;br /&gt;
                      push&lt;br /&gt;
 &lt;br /&gt;
 +---+===+---+&lt;br /&gt;
 | C |=I=| K |&lt;br /&gt;
 +---+===+---+&lt;br /&gt;
          \_/&lt;br /&gt;
          push&lt;br /&gt;
 &lt;br /&gt;
 +===+&lt;br /&gt;
 |=C=|&lt;br /&gt;
 +===+&lt;br /&gt;
 &lt;br /&gt;
         +===+&lt;br /&gt;
         |=K=|&lt;br /&gt;
         +===+&lt;br /&gt;
 &lt;br /&gt;
                 +---+---+===+&lt;br /&gt;
                 | Q | S |=U=|&lt;br /&gt;
                 +---+---+===+&lt;br /&gt;
 &lt;br /&gt;
                 +---+===+&lt;br /&gt;
                 | Q |=S=|&lt;br /&gt;
                 +---+===+&lt;br /&gt;
 &lt;br /&gt;
                 +===+&lt;br /&gt;
                 |=Q=|&lt;br /&gt;
                 +===+&lt;br /&gt;
         &lt;br /&gt;
 +---+---+---+---+---+---+---+&lt;br /&gt;
 | C | I | K | O | Q | S | U |  &lt;br /&gt;
 +---+---+---+---+---+---+---+&lt;br /&gt;
&lt;br /&gt;
==== Alternatives Sortieren kleiner Intervalle ====&lt;br /&gt;
&lt;br /&gt;
Für kleine Arrays (bis zu einer gegebenen Größe K) ist das &amp;quot;Teile und herrsche&amp;quot;-Prinzip nicht die effizienteste Herangehensweise. Insbesondere kann man ein Array mit maximal 3 Elementen direkt sortieren:&lt;br /&gt;
 def sortThree(a, l, r):&lt;br /&gt;
     if r &amp;gt; l and a[l+1] &amp;lt; a[l]:          # Stelle sicher, dass a[l] und a[l+1] relativ zueinander sortiert sind.&lt;br /&gt;
         a[l], a[l+1] = a[l+1], a[l]&lt;br /&gt;
     if r == l + 2:&lt;br /&gt;
         if a[r] &amp;lt; a[l]:                  # Stelle sicher, dass a[l] und a[r] relativ zueinander sortiert sind.&lt;br /&gt;
             a[l], a[r] = a[r], a[l]      # Danach ist a[l] auf jeden Fall das kleinste Element.&lt;br /&gt;
         if a[r] &amp;lt; a[r-1]:                # Stelle sicher, dass a[r-1] und a[r] relativ zueinander sortiert sind.&lt;br /&gt;
             a[r], a[r-1] = a[r-1], a[r]  # Jetzt ist a[r] auf jeden Fall das größte Element und das Array damit sortiert.&lt;br /&gt;
&lt;br /&gt;
In die Funktion &amp;lt;tt&amp;gt;quicksort()&amp;lt;/tt&amp;gt; wird jetzt ein Aufruf dieser Funktion eingefügt:&lt;br /&gt;
     if r &amp;gt; l + 2:&lt;br /&gt;
         # wie bisher&lt;br /&gt;
     elif r &amp;gt; l:&lt;br /&gt;
         sortThree(a, l, r)&lt;br /&gt;
&lt;br /&gt;
==== Günstige Selektion des Pivot-Elements ====&lt;br /&gt;
Durch geschickte Wahl des Pivot-Elements kann man erreichen, dass der ungünstigste Fall (quadratische Laufzeit) nur mit sehr kleiner Wahrscheinlichkeit eintritt. Zwei Möglichkeiten haben sich bewährt:&lt;br /&gt;
# Anstatt des letzten Elements des Teilarrays wählt man ein zufälliges Element (mit Hilfe eines Zufallszahlengenerators). Dadurch wird Quick Sort unempfindlich gegenüber bereits sortierten Arrays, weil die Teilung im Mittel wie bei einem zufällig sortierten Array erfolgt (typischer Fall in obiger Laufzeitberechnung).&lt;br /&gt;
# Median (mittlerer Wert) von drei Elementen: Verwende den Median des ersten, mittleren und letzten Elements jedes Teilarrays als Pivot-Element.&lt;br /&gt;
In beiden Fällen ist es praktisch ausgeschlossen, dass ein Eingabearray so angeordnet ist, dass in jedem Teilarray gerade das kleinste oder größte Element als Pivot gewählt wird. Nur dann könnte der ungünstigste Fall jedoch eintreten, was somit effektiv verhindert wird.&lt;/div&gt;</summary>
		<author><name>Jschleic</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Greedy-Algorithmen_und_Dynamische_Programmierung&amp;diff=2532</id>
		<title>Greedy-Algorithmen und Dynamische Programmierung</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Greedy-Algorithmen_und_Dynamische_Programmierung&amp;diff=2532"/>
				<updated>2008-07-22T10:44:45Z</updated>
		
		<summary type="html">&lt;p&gt;Jschleic: /* Dynamische Programmierung */ Programmierung &amp;lt;-&amp;gt; Optimierung&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Einführung ==&lt;br /&gt;
:Viele Probleme sind durch einen Entscheidungsbaum systematisch lösbar.&lt;br /&gt;
:Dabei wird die zu suchende Lösung auf den optimalen Weg durch den Entscheidungsbaum reduziert.&lt;br /&gt;
&lt;br /&gt;
=== Beispiel ===&lt;br /&gt;
:''Erklärung des Algorithmus ist zu finden in [[Graphen und Graphenalgorithmen]]''&lt;br /&gt;
:Traveling Salesman Problem mit 4 Knoten&lt;br /&gt;
&lt;br /&gt;
[[Image:tsm_points.JPG]]&lt;br /&gt;
&lt;br /&gt;
:'''Dabei entsteht folgender Entscheidungsbaum:'''&lt;br /&gt;
&lt;br /&gt;
[[Image:tsm4.JPG]]&lt;br /&gt;
&lt;br /&gt;
::'''Vorteil des Entscheidungsbaums:''' Lösungsmöglichkeiten werden nicht übersehen&lt;br /&gt;
::'''Nachteil:''' Eventuell muss der gesamte Baum durchsucht werden (exponentielle Komplexität)&lt;br /&gt;
:Um diesen Nachteil auszugleichen gibt es verschiedene Verfahren:&lt;br /&gt;
:* Divide &amp;amp; Conquer (Problem auf triviale Teilprobleme zurückführen, welche jeweils einfach zu lösen sind)&lt;br /&gt;
:* Greedy Algorithmen&lt;br /&gt;
:* Dynamische Programmierung&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Greedy Algorithmen ==&lt;br /&gt;
:Greedy (dt. &amp;quot;Gierig&amp;quot;) Algorithmen entscheiden an jedem Knoten '''lokal''' über die beste Fortsetzung der Suche,&lt;br /&gt;
:d.h. es wird jeweils die beste Entscheidung im Kleinen getroffen - ohne Rücksicht auf Konsequenzen für den gesamten Suchverlauf.&lt;br /&gt;
&lt;br /&gt;
=== Beispiele ===&lt;br /&gt;
==== Anwendung beim Traveling Salesman Problem ====&lt;br /&gt;
:''Erklärung des Algorithmus ist zu finden in [[Graphen und Graphenalgorithmen]]''&lt;br /&gt;
:Reise immer zum nächstgelegenen, noch nicht besuchten Knoten:&lt;br /&gt;
&lt;br /&gt;
[[Image:tsm_greedyb.JPG]]&lt;br /&gt;
&lt;br /&gt;
:In diesem Beispiel wurde eine optimale Lösung gefunden. '''Dies muss im Allgemeinen aber nicht immer der Fall sein!'''&lt;br /&gt;
&lt;br /&gt;
==== Anwendung beim Algorithmus von Kruskal für Minium Spanning Tree ====&lt;br /&gt;
:''Erklärung des Algorithmus ist zu finden in [[Graphen und Graphenalgorithmen]]''&lt;br /&gt;
:* Sortiere die Kanten nach Gewicht&lt;br /&gt;
:* Wähle stets die Kante mit niedrigstem Gewicht (d.h. im Allgemeinen die nächsgelegene), die keinen Zyklus verursacht&lt;br /&gt;
&lt;br /&gt;
:Hierbei wird der Minimum Spanning Tree stets gefunden.&lt;br /&gt;
&lt;br /&gt;
== Dynamische Programmierung ==&lt;br /&gt;
(''Programmierung'' bezieht sich hier nicht auf spezielle Programmiersprachen, sondern meint vielmehr die Optimierung des Programmablaufs)&lt;br /&gt;
&lt;br /&gt;
:Oft ist dasselbe Teilproblem in mehreren Pfaden vorhanden.&lt;br /&gt;
=== Beispiel ===&lt;br /&gt;
&lt;br /&gt;
[[Image:fib1.JPG]]&lt;br /&gt;
&lt;br /&gt;
:Im Beispiel mit Fibonacci-Zahlen wird Fib(2) gleich dreimal benötigt.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
:Zur Erinnerung: Die Fibonacci-Folge &amp;lt;math&amp;gt;(f_0, f_1,\ldots)&amp;lt;/math&amp;gt; ist durch das rekursive Bildungsgesetz&lt;br /&gt;
:&amp;lt;math&amp;gt; f_n = f_{n-1} + f_{n-2}\ &amp;lt;/math&amp;gt; &amp;amp;nbsp; für &amp;lt;math&amp;gt;n\geq 2&amp;lt;/math&amp;gt;&lt;br /&gt;
:mit den Anfangswerten&lt;br /&gt;
:&amp;lt;math&amp;gt;f_0=0\ &amp;lt;/math&amp;gt; &amp;amp;nbsp; und &amp;amp;nbsp; &amp;lt;math&amp;gt;f_1=1\ &amp;lt;/math&amp;gt;&lt;br /&gt;
:definiert.&lt;br /&gt;
&lt;br /&gt;
=== Konzept der Dynamische Programmierung ===&lt;br /&gt;
:Jedes Teilproblem soll nur einmal gelöst werden, d.h. einige Knoten werden mehrmals genutzt:&lt;br /&gt;
&lt;br /&gt;
[[Image:fib2.JPG]]&lt;br /&gt;
&lt;br /&gt;
:Wie im Beispiel erkennbar, hat sich die Zahl der Knoten drastisch reduziert (von 9 auf 5). &lt;br /&gt;
:Allerdings müssen die '''Graphen jetzt gerichtet''' sein.&lt;br /&gt;
&lt;br /&gt;
:Wenn der neue Graph '''azyklisch''' ist, kann man die Teilprobleme so anordnen, dass jedes&lt;br /&gt;
:* nur einmal gelöst wird &lt;br /&gt;
:* nur von bereits gelösten Teilproblemen abhängt&lt;br /&gt;
&lt;br /&gt;
:Wenn der Graph nicht azyklisch ist (weil z.B. Teilproblem A die Lösung von Teilproblem B erfordert und umgekehrt),&lt;br /&gt;
:ist die Dynamische Programmierung auf dieses Problem nicht anwendbar.&lt;br /&gt;
&lt;br /&gt;
==== Dynamisch programmierter Dijkstra Alrogithmus ====&lt;br /&gt;
:''Erklärung des Algorithmus ist zu finden in [[Graphen und Graphenalgorithmen]]''&lt;br /&gt;
:Löse Teilprobleme entsprechend ihrer Priorität, d.h. Priorität definiert die Ordnung&lt;br /&gt;
&lt;br /&gt;
:'''Problem:''' Der Suchbaum ist bei diesem Algorithmus ungerichtet&lt;br /&gt;
&lt;br /&gt;
:'''Lösung:''' Die Richtung der Kanten wird festgelegt, wenn man die Nachbarn eines Knotens in die Queue eingefügt&lt;br /&gt;
:Wenn man den Abstand vom Start bestimmt (Teilproblem), ist der Abstand von allen näher gelegenen bereits bekannt.&lt;br /&gt;
&lt;br /&gt;
[[Image:dijkstra.JPG]]&lt;br /&gt;
&lt;br /&gt;
== Greedy oder Dynamische Programmierung? ==&lt;br /&gt;
:Für viele Probleme gibt es unterschiedliche Entscheidungsräume und/oder unterschiedliche Entscheidungskriterien.&lt;br /&gt;
:Ein und dasselbe Problem kann also mit einer der Darstellungen (Greedy, Dynamische Programmierung, weitere...) effizient lösbar sein, mit anderen eventuell nicht.&lt;br /&gt;
:Das finden einer geeigneten Darstellung ist also eine zentrale Herausforderung.&lt;br /&gt;
&lt;br /&gt;
== Anwendungsbeispiel: Interval Scheduling ==&lt;br /&gt;
:'''gegeben:'''&lt;br /&gt;
::Mehrere Aufgaben mit unterschiedlichen Anfangszeiten &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt; und Endzeiten &amp;lt;math&amp;gt;f_i&amp;lt;/math&amp;gt;. &lt;br /&gt;
::Es kann immer nur eine Aufgabe gleichzeitig bearbeitet werden: zwei Aktivitäten sind kompatibel, wenn deren Zeiten sich nicht überlappen.&lt;br /&gt;
::&amp;lt;math&amp;gt;a_k\,\text{komp}\, a_j  \Leftrightarrow  s_k\geq f_j\, \or \, s_j\geq f_k&amp;lt;/math&amp;gt;&lt;br /&gt;
:'''gesucht:'''&lt;br /&gt;
::Arbeitsplan um möglichst viele Aufgaben nacheinander abzuarbeiten.&lt;br /&gt;
::Dabei haben alle Aufgaben dieselbe Priorität, obwohl die Dauer oft unterschiedlich ist.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Mögliche Lösungsansätze für einen Greedy Algorithmus ===&lt;br /&gt;
:# Wähle (unter kompatiblen) die Aktivität, die als erste startet&lt;br /&gt;
:# '''Wähle (unter kompatiblen) die Aktivität, die als erste endet (oder: die als letzte startet)'''&lt;br /&gt;
:# Wähle (unter kompatiblen) die Aktivität, die am kürzesten dauert&lt;br /&gt;
:# Wähle (unter kompatiblen) die Aktivität, die die wenigsten Inkompatibilitäten (überlappungen mit anderen Aktivitäten) hat&lt;br /&gt;
&lt;br /&gt;
'''Ungünstige Ansätze:'''&lt;br /&gt;
:In den folgenden Beispielen werden die Aktivitäten mit | als Anfangs- bzw Endzeit markiert, --- steht für den Verlauf einer Aktivität.&lt;br /&gt;
:Weiter rechts bedeutet später in der Zeit.&lt;br /&gt;
:'''Gegenbeispiel zu 1.'''&lt;br /&gt;
     &amp;lt;math&amp;gt;L_1=&amp;lt;/math&amp;gt;  |--| |--| |--| |--|&lt;br /&gt;
     &amp;lt;math&amp;gt;L_2=&amp;lt;/math&amp;gt;|---------------------|&lt;br /&gt;
     &amp;lt;math&amp;gt;|L_1|&amp;lt;/math&amp;gt;=4 &amp;lt;math&amp;gt;|L_2|&amp;lt;/math&amp;gt;=1&lt;br /&gt;
:Der Ansatz würde Lösung 2 wählen, da die lange Aktivität am frühesten beginnt. Es wird dann nur 1 statt der optimalen 4 abgearbeitet.&lt;br /&gt;
:'''Gegenbeispiel zu 3.'''&lt;br /&gt;
     &amp;lt;math&amp;gt;L_1=&amp;lt;/math&amp;gt;|---------| |---------|&lt;br /&gt;
     &amp;lt;math&amp;gt;L_2=&amp;lt;/math&amp;gt;        |----|&lt;br /&gt;
     &amp;lt;math&amp;gt;|L_1|&amp;lt;/math&amp;gt;=2 &amp;lt;math&amp;gt;|L_2|&amp;lt;/math&amp;gt;=1&lt;br /&gt;
:Der Ansatz würde Lösung 2 wählen, da die mittlere Aktivität am kürzesten dauert. Es wird dann nur 1 statt der optimalen 2 abgearbeitet.&lt;br /&gt;
:'''Gegenbeispiel zu 4. (Anzahl der Inkompatibilitäten stehen jeweils in der Mitte der Aktivität)'''&lt;br /&gt;
     |-3-| |-4-| |-4-| |-3-|&lt;br /&gt;
        |-4-| |-2-| |-4-|&lt;br /&gt;
        |-4-|       |-4-|&lt;br /&gt;
        |-4-|       |-4-|&lt;br /&gt;
:Der Ansatz würde erst die mit 2, dann die beiden mit 3 Inkompatibilitäten wählen. Es werden dann nur 3 statt der optimalen 4 (obere Zeile) abgearbeitet.&lt;br /&gt;
&lt;br /&gt;
Es verbleibt der 2. Ansatz, dessen Optimalität noch zu beweisen ist...&lt;br /&gt;
&lt;br /&gt;
== Greedy Stays Ahead ==&lt;br /&gt;
==== Idee der Beweismethode ====&lt;br /&gt;
'''Es genügt zu zeigen:''' &lt;br /&gt;
:die Greedy-Lösung ist nicht schlechter als die optimale Lösung&lt;br /&gt;
&lt;br /&gt;
=== Beweis der Optimalität des 2. Ansatzes mit ''Greedy Stays Ahead'' ===&lt;br /&gt;
==== Ansatz ====&lt;br /&gt;
:Wähle (unter kompatiblen) die Aktivität, die als erste endet (oder: die als letzte startet)&lt;br /&gt;
:Die Wahl dieses Ansatzes sei &amp;lt;math&amp;gt;U={i_1,...,i_k}&amp;lt;/math&amp;gt;.&lt;br /&gt;
:Eine (unbekannte) optimale Lösung sei &amp;lt;math&amp;gt;O={j_1,...,j_m}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==== Ziel ====&lt;br /&gt;
:Die Lösung des Ansatzes soll genausoviele Aktivitäten schaffen wie die optimale Lösung (d.h. k=m)&lt;br /&gt;
==== Voraussetzungen ====&lt;br /&gt;
:* Sortiere &amp;lt;math&amp;gt;i_1,...,i_k&amp;lt;/math&amp;gt; nach aufsteigender Endzeit &amp;lt;math&amp;gt;f_i&amp;lt;/math&amp;gt;&lt;br /&gt;
:* Sortiere &amp;lt;math&amp;gt;j_1,...,j_m&amp;lt;/math&amp;gt; nach aufsteigender Endzeit &amp;lt;math&amp;gt;f_j&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(Da die Aktivitäten kompatibel sind, werden die Anfangszeiten automatisch auch sortiert)&lt;br /&gt;
&lt;br /&gt;
==== Schritt 1 ====&lt;br /&gt;
:Für die Indizes &amp;lt;math&amp;gt;p\leq r&amp;lt;/math&amp;gt; (inbesondere &amp;lt;math&amp;gt;p=r&amp;lt;/math&amp;gt;) gilt: &amp;lt;math&amp;gt;f(i_p)\leq f(j_r)&amp;lt;/math&amp;gt;&lt;br /&gt;
==== Beweis durch vollständige Induktion ====&lt;br /&gt;
:'''Induktions-Anfang:'''&lt;br /&gt;
::&amp;lt;math&amp;gt;f(i_1)\leq f(j_1)&amp;lt;/math&amp;gt;, da &amp;lt;math&amp;gt;i_1&amp;lt;/math&amp;gt; die erste Aktivität ist, die überhaupt endet&lt;br /&gt;
:'''Induktions-Voraussetzung:'''&lt;br /&gt;
::&amp;lt;math&amp;gt;f(i_{r-1})\leq f(j_{r-1})&amp;lt;/math&amp;gt;&lt;br /&gt;
:'''Induktions-Schritt:'''&lt;br /&gt;
::Wegen Kompatibilität gilt:&lt;br /&gt;
::&amp;lt;math&amp;gt;f(j_{r-1})\leq s(j_r)&amp;lt;/math&amp;gt;&lt;br /&gt;
::=&amp;gt; &amp;lt;math&amp;gt;f(i_{r-1})\leq s(i_r)&amp;lt;/math&amp;gt;&lt;br /&gt;
::=&amp;gt; Die Greedy Strategie ''kann'' Aktivität &amp;lt;math&amp;gt;j_r&amp;lt;/math&amp;gt; wählen, denn sie ist kompatibel mit &amp;lt;math&amp;gt;i_{r-1}&amp;lt;/math&amp;gt;&lt;br /&gt;
::* Wenn die Greedy Strategie tatsächlich &amp;lt;math&amp;gt;j_r&amp;lt;/math&amp;gt; wählt, folgt daraus: &lt;br /&gt;
:::&amp;lt;math&amp;gt;f(i_r)=f(j_r)&amp;lt;/math&amp;gt;&lt;br /&gt;
::* Wenn nicht, kann nur gelten:&lt;br /&gt;
:::&amp;lt;math&amp;gt;f(i_r)\leq f(j_r)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Schritt 2 ====&lt;br /&gt;
:Zu zeigen: &amp;lt;math&amp;gt;k=m&amp;lt;/math&amp;gt;&lt;br /&gt;
==== Beweis durch Widerspruchsannahme ====&lt;br /&gt;
:* Falls &amp;lt;math&amp;gt;m&amp;lt;k&amp;lt;/math&amp;gt;, wäre die Lösung der Strategie besser als die optimale.&lt;br /&gt;
:* Angenommen &amp;lt;math&amp;gt;m&amp;gt;k&amp;lt;/math&amp;gt;, dann enthält &amp;lt;math&amp;gt;O&amp;lt;/math&amp;gt; eine Aktivität &amp;lt;math&amp;gt;j_{k+1}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
:Nach Schritt 1 gilt:&lt;br /&gt;
::&amp;lt;math&amp;gt;f(i_k)\leq f(j_k)\leq f(j_{k+1})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:Wegen Kompatibilität gilt aber:&lt;br /&gt;
::&amp;lt;math&amp;gt;s(j_{k+1})\geq f(j_k)\geq f(i_k)&amp;lt;/math&amp;gt;&lt;br /&gt;
:-&amp;gt; Die Greedy Strategie hätte also noch die Aktivität &amp;lt;math&amp;gt;j_{k+1}&amp;lt;/math&amp;gt; wählen können.&lt;br /&gt;
:-&amp;gt; Widerspruch zur Annahme, dass die Greedy Strategie durchgelaufen ist, bis keine Aktivität mehr hinzugefügt werden kann&lt;br /&gt;
:-&amp;gt; m&amp;gt;k ist falsch&lt;br /&gt;
:-&amp;gt; m=k ist richtig&lt;br /&gt;
&lt;br /&gt;
=== Beispiel zur Dynamischen Programmierung: Weighted Intervall Scheduling ===&lt;br /&gt;
:Die Problemstellung ähnelt dem des normalen Intervall Scheduling, hier haben die Aktivitäten aber Gewichte &amp;lt;math&amp;gt;w_i&amp;lt;/math&amp;gt;&lt;br /&gt;
:(z.B. Bringt eine längere Aufgabe in einem Übunsgzettel in der Regel auch mehr Punkte, d.h. sie hat eine hohe Gewichtung)&lt;br /&gt;
==== Ziel ====&lt;br /&gt;
:Wähle die Aktivitäten so, dass der Gewinn (Summe der Gewichtungen der bearbeiteten Aktivitäten) maximal wird.&lt;br /&gt;
==== Ansatz ====&lt;br /&gt;
:* Sortiere Aktivitäten nach ihrer Endzeit.&lt;br /&gt;
:* Definiere eine Funktion &amp;lt;math&amp;gt;p(i)&amp;lt;/math&amp;gt;, welche für die Aktivität steht, die vor &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt; endet, mit &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt; kompatibel ist, unter allen Aktivitäten mit diesen Eigenschaften die letzte (d.h. mit der spätesten Endzeit) ist. &lt;br /&gt;
&lt;br /&gt;
In folgendem Beispiel wird die Aktivität &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt; mit der Symbolik |-!-| betrachtet um deren p-Funktion zu evaluieren.&lt;br /&gt;
&lt;br /&gt;
Für die p-Funktion kommen lediglich die Funktionen mit der Symbolik |===| und |====| in Frage, die untere der beiden ist die gesuchte Aktivität.&lt;br /&gt;
    |====|        |-!-| |--|&lt;br /&gt;
        |===| |-----|         |----|&lt;br /&gt;
&lt;br /&gt;
:Trivial ist, dass &amp;lt;math&amp;gt;a_n&amp;lt;/math&amp;gt; entweder zur Lösung gehört, oder nicht:&lt;br /&gt;
&lt;br /&gt;
[[Image:wis1.JPG]]&lt;br /&gt;
&lt;br /&gt;
:Dadurch ergibt sich folgende Funktion:&lt;br /&gt;
:&amp;lt;math&amp;gt;OPT(n)=\begin{cases}&lt;br /&gt;
\ \;\, \{a_n\}\cup OPT(p(n))\\&lt;br /&gt;
\ \;\, OPT(a_{n-1})&lt;br /&gt;
\end{cases}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:Um den höchstmöglichen Gewinn zu erzielen, wird &amp;lt;math&amp;gt;\{a_n\}\cup OPT(p(n))&amp;lt;/math&amp;gt; verwendet falls gilt:&lt;br /&gt;
:&amp;lt;math&amp;gt;w_n + Gewinn(OPT(p(n))\leq Gewinn(OPT(n-1))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:Ansonsten wird &amp;lt;math&amp;gt;OPT(a_{n-1})&amp;lt;/math&amp;gt; angewandt.&lt;br /&gt;
&lt;br /&gt;
== Erinnerung: Intervalle ==&lt;br /&gt;
&lt;br /&gt;
[[Image:bild1.JPG]]&lt;br /&gt;
&lt;br /&gt;
Sortiere Intervalle nach aufsteigender &amp;lt;math&amp;gt; f_{i} &amp;lt;/math&amp;gt;:&lt;br /&gt;
:Gewinn &amp;lt;math&amp;gt;(a_{n})&amp;lt;/math&amp;gt; = max &amp;lt;math&amp;gt;(w_{n})&amp;lt;/math&amp;gt; + Gewinn (p(n)), Gewinn &amp;lt;math&amp;gt; (a_{n-1})&amp;lt;/math&amp;gt;&lt;br /&gt;
:Gewinn &amp;lt;math&amp;gt; (a_{n-1})&amp;lt;/math&amp;gt; = max (&amp;lt;math&amp;gt;w_{n-1}&amp;lt;/math&amp;gt; + Gewinn &amp;lt;math&amp;gt;(p(n-1)&amp;lt;/math&amp;gt;), Gewinn &amp;lt;math&amp;gt; (a_{n-2})&amp;lt;/math&amp;gt;&lt;br /&gt;
:usw.&lt;br /&gt;
&lt;br /&gt;
[[Image:bild2.JPG]]&lt;br /&gt;
&lt;br /&gt;
Bearbeite Teilprobleme in Reihenfolge der Sortierung&lt;br /&gt;
:=&amp;gt; azyklischer Graph&lt;br /&gt;
:(a2 hängt von a1 ab, a3 von a2, a4 von a2 und a3 usw.)&lt;br /&gt;
&lt;br /&gt;
Wie erkennt man, ob der Graph azyklisch ist, und wie fndet man die Reihenfolge?&lt;br /&gt;
* azyklischer, gerichteter Graph: „directed acyclic graph“ – DAG&lt;br /&gt;
* Ein Graph ist genau dann ein DAG, wenn es eine topologische Sortierung der Knoten gibt.&lt;br /&gt;
:Def.: Zeichne die Knoten so auf eine Gerade, dass alle Kanten nach rechts/in dieselbe Richtung zeigen&lt;br /&gt;
&lt;br /&gt;
[[Image:bild3.JPG]]&lt;br /&gt;
&lt;br /&gt;
:=&amp;gt; arbeite topologische Sortierung von rechts nach links ab.&lt;br /&gt;
&lt;br /&gt;
=== Beispiel ===&lt;br /&gt;
Wie erklärt man einem zerstreuten Professor, wie er sich morgens anziehen soll?&lt;br /&gt;
&lt;br /&gt;
[[Image:bild4.JPG]]&lt;br /&gt;
&lt;br /&gt;
:=&amp;gt; Die topologische Sortierung ist hier nicht eindeutig, z.B. ist nicht klar, ob zuerst die Strümpfe angezogen werden sollen oder die Unterhose. Wann die Uhr angelegt werden soll, ist überhaupt nicht festgelegt. Mit dieser Beschreibung käme der arme Professor wohl kaum zurecht.&lt;br /&gt;
&lt;br /&gt;
[[Image:bild5.JPG]]&lt;br /&gt;
&lt;br /&gt;
== Zwei Algorithmen zum Finden der topologischen Sortierung ==&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 vorhandenen Knoten (also 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;
Bild: Ein zyklischer Graph&lt;br /&gt;
&lt;br /&gt;
=== Algorithmus 2 ===&lt;br /&gt;
Verwende Tiefensuche, um die Finishing Time zu bestimmen&lt;br /&gt;
: =&amp;gt; zeichne Knoten nach abnehmender Finishing Time auf die Gerade&lt;br /&gt;
&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;
[[Image:bild7.JPG]]&lt;br /&gt;
&lt;br /&gt;
Fälle:&lt;br /&gt;
# Matche a[i] mit b[j]. Falls a[i] == b[j], ist das gut. Andernfalls entstehen Kosten &amp;lt;math&amp;gt;K_{a[i], b[j]}&amp;lt;/math&amp;gt;&lt;br /&gt;
# Wir überspringen a[i] oder b[j], =&amp;gt; Kosten L&lt;br /&gt;
&lt;br /&gt;
:gesucht: Alignment mit minimalen Kosten&lt;br /&gt;
&lt;br /&gt;
[[Image:bild8.JPG]]&lt;br /&gt;
&lt;br /&gt;
Lösung:&lt;br /&gt;
:Suche kürzesten Pfad von links oben nach rechts unten (z.B. mit dem [[Graphen und Graphenalgorithmen#Algorithmus von Dijkstra|Algorithmus von Dijkstra]])&lt;br /&gt;
:In unserem Beispiel von oben:&lt;br /&gt;
&lt;br /&gt;
[[Image:bild9.JPG]]&lt;br /&gt;
&lt;br /&gt;
=== Problemlösung durch einen Entscheidungsbaum ===&lt;br /&gt;
Greedy Algorithm und dynamische Programmierung: &lt;br /&gt;
Transformationen des Problems, sodass '''nicht''' der ganze Entscheidungsbaum durchlaufen werden muss.&lt;br /&gt;
=&amp;gt; effizient&lt;br /&gt;
&lt;br /&gt;
* bei vielen Problemen ist keine Möglichkeit bekannt, das vollständige Durchlaufen des Entscheidungsbaumes zu vermeiden, z.B. [[Graphen_und_Graphenalgorithmen#Problem_des_Handlungsreisenden | Problem des Handlungsreisenden]] (TSP), 3-SAT&lt;br /&gt;
* Frage: Gibt es prinzipiell keinen effizienten Algorithmus, oder sind wir nur zu blöd?&lt;br /&gt;
* Derzeitiger Stand: viele dieser Probleme sind fundamental äquivalent „NP complete problems“ – „NP vollständig“ (NP = &amp;quot;nicht-deterministisch polynomiell&amp;quot;)&lt;/div&gt;</summary>
		<author><name>Jschleic</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Greedy-Algorithmen_und_Dynamische_Programmierung&amp;diff=2531</id>
		<title>Greedy-Algorithmen und Dynamische Programmierung</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Greedy-Algorithmen_und_Dynamische_Programmierung&amp;diff=2531"/>
				<updated>2008-07-22T10:41:37Z</updated>
		
		<summary type="html">&lt;p&gt;Jschleic: /* Problemlösung durch einen Entscheidungsbaum */ Link korrigiert&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Einführung ==&lt;br /&gt;
:Viele Probleme sind durch einen Entscheidungsbaum systematisch lösbar.&lt;br /&gt;
:Dabei wird die zu suchende Lösung auf den optimalen Weg durch den Entscheidungsbaum reduziert.&lt;br /&gt;
&lt;br /&gt;
=== Beispiel ===&lt;br /&gt;
:''Erklärung des Algorithmus ist zu finden in [[Graphen und Graphenalgorithmen]]''&lt;br /&gt;
:Traveling Salesman Problem mit 4 Knoten&lt;br /&gt;
&lt;br /&gt;
[[Image:tsm_points.JPG]]&lt;br /&gt;
&lt;br /&gt;
:'''Dabei entsteht folgender Entscheidungsbaum:'''&lt;br /&gt;
&lt;br /&gt;
[[Image:tsm4.JPG]]&lt;br /&gt;
&lt;br /&gt;
::'''Vorteil des Entscheidungsbaums:''' Lösungsmöglichkeiten werden nicht übersehen&lt;br /&gt;
::'''Nachteil:''' Eventuell muss der gesamte Baum durchsucht werden (exponentielle Komplexität)&lt;br /&gt;
:Um diesen Nachteil auszugleichen gibt es verschiedene Verfahren:&lt;br /&gt;
:* Divide &amp;amp; Conquer (Problem auf triviale Teilprobleme zurückführen, welche jeweils einfach zu lösen sind)&lt;br /&gt;
:* Greedy Algorithmen&lt;br /&gt;
:* Dynamische Programmierung&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Greedy Algorithmen ==&lt;br /&gt;
:Greedy (dt. &amp;quot;Gierig&amp;quot;) Algorithmen entscheiden an jedem Knoten '''lokal''' über die beste Fortsetzung der Suche,&lt;br /&gt;
:d.h. es wird jeweils die beste Entscheidung im Kleinen getroffen - ohne Rücksicht auf Konsequenzen für den gesamten Suchverlauf.&lt;br /&gt;
&lt;br /&gt;
=== Beispiele ===&lt;br /&gt;
==== Anwendung beim Traveling Salesman Problem ====&lt;br /&gt;
:''Erklärung des Algorithmus ist zu finden in [[Graphen und Graphenalgorithmen]]''&lt;br /&gt;
:Reise immer zum nächstgelegenen, noch nicht besuchten Knoten:&lt;br /&gt;
&lt;br /&gt;
[[Image:tsm_greedyb.JPG]]&lt;br /&gt;
&lt;br /&gt;
:In diesem Beispiel wurde eine optimale Lösung gefunden. '''Dies muss im Allgemeinen aber nicht immer der Fall sein!'''&lt;br /&gt;
&lt;br /&gt;
==== Anwendung beim Algorithmus von Kruskal für Minium Spanning Tree ====&lt;br /&gt;
:''Erklärung des Algorithmus ist zu finden in [[Graphen und Graphenalgorithmen]]''&lt;br /&gt;
:* Sortiere die Kanten nach Gewicht&lt;br /&gt;
:* Wähle stets die Kante mit niedrigstem Gewicht (d.h. im Allgemeinen die nächsgelegene), die keinen Zyklus verursacht&lt;br /&gt;
&lt;br /&gt;
:Hierbei wird der Minimum Spanning Tree stets gefunden.&lt;br /&gt;
&lt;br /&gt;
== Dynamische Programmierung ==&lt;br /&gt;
(''Programmierung'' hat hier eine Bedeutung die sich nicht auf Programmiersprachen bezieht)&lt;br /&gt;
&lt;br /&gt;
:Oft ist dasselbe Teilproblem in mehreren Pfaden vorhanden.&lt;br /&gt;
=== Beispiel ===&lt;br /&gt;
&lt;br /&gt;
[[Image:fib1.JPG]]&lt;br /&gt;
&lt;br /&gt;
:Im Beispiel mit Fibonacci-Zahlen wird Fib(2) gleich dreimal benötigt.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
:Zur Erinnerung: Die Fibonacci-Folge &amp;lt;math&amp;gt;(f_0, f_1,\ldots)&amp;lt;/math&amp;gt; ist durch das rekursive Bildungsgesetz&lt;br /&gt;
:&amp;lt;math&amp;gt; f_n = f_{n-1} + f_{n-2}\ &amp;lt;/math&amp;gt; &amp;amp;nbsp; für &amp;lt;math&amp;gt;n\geq 2&amp;lt;/math&amp;gt;&lt;br /&gt;
:mit den Anfangswerten&lt;br /&gt;
:&amp;lt;math&amp;gt;f_0=0\ &amp;lt;/math&amp;gt; &amp;amp;nbsp; und &amp;amp;nbsp; &amp;lt;math&amp;gt;f_1=1\ &amp;lt;/math&amp;gt;&lt;br /&gt;
:definiert.&lt;br /&gt;
&lt;br /&gt;
=== Konzept der Dynamische Programmierung ===&lt;br /&gt;
:Jedes Teilproblem soll nur einmal gelöst werden, d.h. einige Knoten werden mehrmals genutzt:&lt;br /&gt;
&lt;br /&gt;
[[Image:fib2.JPG]]&lt;br /&gt;
&lt;br /&gt;
:Wie im Beispiel erkennbar, hat sich die Zahl der Knoten drastisch reduziert (von 9 auf 5). &lt;br /&gt;
:Allerdings müssen die '''Graphen jetzt gerichtet''' sein.&lt;br /&gt;
&lt;br /&gt;
:Wenn der neue Graph '''azyklisch''' ist, kann man die Teilprobleme so anordnen, dass jedes&lt;br /&gt;
:* nur einmal gelöst wird &lt;br /&gt;
:* nur von bereits gelösten Teilproblemen abhängt&lt;br /&gt;
&lt;br /&gt;
:Wenn der Graph nicht azyklisch ist (weil z.B. Teilproblem A die Lösung von Teilproblem B erfordert und umgekehrt),&lt;br /&gt;
:ist die Dynamische Programmierung auf dieses Problem nicht anwendbar.&lt;br /&gt;
&lt;br /&gt;
==== Dynamisch programmierter Dijkstra Alrogithmus ====&lt;br /&gt;
:''Erklärung des Algorithmus ist zu finden in [[Graphen und Graphenalgorithmen]]''&lt;br /&gt;
:Löse Teilprobleme entsprechend ihrer Priorität, d.h. Priorität definiert die Ordnung&lt;br /&gt;
&lt;br /&gt;
:'''Problem:''' Der Suchbaum ist bei diesem Algorithmus ungerichtet&lt;br /&gt;
&lt;br /&gt;
:'''Lösung:''' Die Richtung der Kanten wird festgelegt, wenn man die Nachbarn eines Knotens in die Queue eingefügt&lt;br /&gt;
:Wenn man den Abstand vom Start bestimmt (Teilproblem), ist der Abstand von allen näher gelegenen bereits bekannt.&lt;br /&gt;
&lt;br /&gt;
[[Image:dijkstra.JPG]]&lt;br /&gt;
&lt;br /&gt;
== Greedy oder Dynamische Programmierung? ==&lt;br /&gt;
:Für viele Probleme gibt es unterschiedliche Entscheidungsräume und/oder unterschiedliche Entscheidungskriterien.&lt;br /&gt;
:Ein und dasselbe Problem kann also mit einer der Darstellungen (Greedy, Dynamische Programmierung, weitere...) effizient lösbar sein, mit anderen eventuell nicht.&lt;br /&gt;
:Das finden einer geeigneten Darstellung ist also eine zentrale Herausforderung.&lt;br /&gt;
&lt;br /&gt;
== Anwendungsbeispiel: Interval Scheduling ==&lt;br /&gt;
:'''gegeben:'''&lt;br /&gt;
::Mehrere Aufgaben mit unterschiedlichen Anfangszeiten &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt; und Endzeiten &amp;lt;math&amp;gt;f_i&amp;lt;/math&amp;gt;. &lt;br /&gt;
::Es kann immer nur eine Aufgabe gleichzeitig bearbeitet werden: zwei Aktivitäten sind kompatibel, wenn deren Zeiten sich nicht überlappen.&lt;br /&gt;
::&amp;lt;math&amp;gt;a_k\,\text{komp}\, a_j  \Leftrightarrow  s_k\geq f_j\, \or \, s_j\geq f_k&amp;lt;/math&amp;gt;&lt;br /&gt;
:'''gesucht:'''&lt;br /&gt;
::Arbeitsplan um möglichst viele Aufgaben nacheinander abzuarbeiten.&lt;br /&gt;
::Dabei haben alle Aufgaben dieselbe Priorität, obwohl die Dauer oft unterschiedlich ist.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Mögliche Lösungsansätze für einen Greedy Algorithmus ===&lt;br /&gt;
:# Wähle (unter kompatiblen) die Aktivität, die als erste startet&lt;br /&gt;
:# '''Wähle (unter kompatiblen) die Aktivität, die als erste endet (oder: die als letzte startet)'''&lt;br /&gt;
:# Wähle (unter kompatiblen) die Aktivität, die am kürzesten dauert&lt;br /&gt;
:# Wähle (unter kompatiblen) die Aktivität, die die wenigsten Inkompatibilitäten (überlappungen mit anderen Aktivitäten) hat&lt;br /&gt;
&lt;br /&gt;
'''Ungünstige Ansätze:'''&lt;br /&gt;
:In den folgenden Beispielen werden die Aktivitäten mit | als Anfangs- bzw Endzeit markiert, --- steht für den Verlauf einer Aktivität.&lt;br /&gt;
:Weiter rechts bedeutet später in der Zeit.&lt;br /&gt;
:'''Gegenbeispiel zu 1.'''&lt;br /&gt;
     &amp;lt;math&amp;gt;L_1=&amp;lt;/math&amp;gt;  |--| |--| |--| |--|&lt;br /&gt;
     &amp;lt;math&amp;gt;L_2=&amp;lt;/math&amp;gt;|---------------------|&lt;br /&gt;
     &amp;lt;math&amp;gt;|L_1|&amp;lt;/math&amp;gt;=4 &amp;lt;math&amp;gt;|L_2|&amp;lt;/math&amp;gt;=1&lt;br /&gt;
:Der Ansatz würde Lösung 2 wählen, da die lange Aktivität am frühesten beginnt. Es wird dann nur 1 statt der optimalen 4 abgearbeitet.&lt;br /&gt;
:'''Gegenbeispiel zu 3.'''&lt;br /&gt;
     &amp;lt;math&amp;gt;L_1=&amp;lt;/math&amp;gt;|---------| |---------|&lt;br /&gt;
     &amp;lt;math&amp;gt;L_2=&amp;lt;/math&amp;gt;        |----|&lt;br /&gt;
     &amp;lt;math&amp;gt;|L_1|&amp;lt;/math&amp;gt;=2 &amp;lt;math&amp;gt;|L_2|&amp;lt;/math&amp;gt;=1&lt;br /&gt;
:Der Ansatz würde Lösung 2 wählen, da die mittlere Aktivität am kürzesten dauert. Es wird dann nur 1 statt der optimalen 2 abgearbeitet.&lt;br /&gt;
:'''Gegenbeispiel zu 4. (Anzahl der Inkompatibilitäten stehen jeweils in der Mitte der Aktivität)'''&lt;br /&gt;
     |-3-| |-4-| |-4-| |-3-|&lt;br /&gt;
        |-4-| |-2-| |-4-|&lt;br /&gt;
        |-4-|       |-4-|&lt;br /&gt;
        |-4-|       |-4-|&lt;br /&gt;
:Der Ansatz würde erst die mit 2, dann die beiden mit 3 Inkompatibilitäten wählen. Es werden dann nur 3 statt der optimalen 4 (obere Zeile) abgearbeitet.&lt;br /&gt;
&lt;br /&gt;
Es verbleibt der 2. Ansatz, dessen Optimalität noch zu beweisen ist...&lt;br /&gt;
&lt;br /&gt;
== Greedy Stays Ahead ==&lt;br /&gt;
==== Idee der Beweismethode ====&lt;br /&gt;
'''Es genügt zu zeigen:''' &lt;br /&gt;
:die Greedy-Lösung ist nicht schlechter als die optimale Lösung&lt;br /&gt;
&lt;br /&gt;
=== Beweis der Optimalität des 2. Ansatzes mit ''Greedy Stays Ahead'' ===&lt;br /&gt;
==== Ansatz ====&lt;br /&gt;
:Wähle (unter kompatiblen) die Aktivität, die als erste endet (oder: die als letzte startet)&lt;br /&gt;
:Die Wahl dieses Ansatzes sei &amp;lt;math&amp;gt;U={i_1,...,i_k}&amp;lt;/math&amp;gt;.&lt;br /&gt;
:Eine (unbekannte) optimale Lösung sei &amp;lt;math&amp;gt;O={j_1,...,j_m}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==== Ziel ====&lt;br /&gt;
:Die Lösung des Ansatzes soll genausoviele Aktivitäten schaffen wie die optimale Lösung (d.h. k=m)&lt;br /&gt;
==== Voraussetzungen ====&lt;br /&gt;
:* Sortiere &amp;lt;math&amp;gt;i_1,...,i_k&amp;lt;/math&amp;gt; nach aufsteigender Endzeit &amp;lt;math&amp;gt;f_i&amp;lt;/math&amp;gt;&lt;br /&gt;
:* Sortiere &amp;lt;math&amp;gt;j_1,...,j_m&amp;lt;/math&amp;gt; nach aufsteigender Endzeit &amp;lt;math&amp;gt;f_j&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(Da die Aktivitäten kompatibel sind, werden die Anfangszeiten automatisch auch sortiert)&lt;br /&gt;
&lt;br /&gt;
==== Schritt 1 ====&lt;br /&gt;
:Für die Indizes &amp;lt;math&amp;gt;p\leq r&amp;lt;/math&amp;gt; (inbesondere &amp;lt;math&amp;gt;p=r&amp;lt;/math&amp;gt;) gilt: &amp;lt;math&amp;gt;f(i_p)\leq f(j_r)&amp;lt;/math&amp;gt;&lt;br /&gt;
==== Beweis durch vollständige Induktion ====&lt;br /&gt;
:'''Induktions-Anfang:'''&lt;br /&gt;
::&amp;lt;math&amp;gt;f(i_1)\leq f(j_1)&amp;lt;/math&amp;gt;, da &amp;lt;math&amp;gt;i_1&amp;lt;/math&amp;gt; die erste Aktivität ist, die überhaupt endet&lt;br /&gt;
:'''Induktions-Voraussetzung:'''&lt;br /&gt;
::&amp;lt;math&amp;gt;f(i_{r-1})\leq f(j_{r-1})&amp;lt;/math&amp;gt;&lt;br /&gt;
:'''Induktions-Schritt:'''&lt;br /&gt;
::Wegen Kompatibilität gilt:&lt;br /&gt;
::&amp;lt;math&amp;gt;f(j_{r-1})\leq s(j_r)&amp;lt;/math&amp;gt;&lt;br /&gt;
::=&amp;gt; &amp;lt;math&amp;gt;f(i_{r-1})\leq s(i_r)&amp;lt;/math&amp;gt;&lt;br /&gt;
::=&amp;gt; Die Greedy Strategie ''kann'' Aktivität &amp;lt;math&amp;gt;j_r&amp;lt;/math&amp;gt; wählen, denn sie ist kompatibel mit &amp;lt;math&amp;gt;i_{r-1}&amp;lt;/math&amp;gt;&lt;br /&gt;
::* Wenn die Greedy Strategie tatsächlich &amp;lt;math&amp;gt;j_r&amp;lt;/math&amp;gt; wählt, folgt daraus: &lt;br /&gt;
:::&amp;lt;math&amp;gt;f(i_r)=f(j_r)&amp;lt;/math&amp;gt;&lt;br /&gt;
::* Wenn nicht, kann nur gelten:&lt;br /&gt;
:::&amp;lt;math&amp;gt;f(i_r)\leq f(j_r)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Schritt 2 ====&lt;br /&gt;
:Zu zeigen: &amp;lt;math&amp;gt;k=m&amp;lt;/math&amp;gt;&lt;br /&gt;
==== Beweis durch Widerspruchsannahme ====&lt;br /&gt;
:* Falls &amp;lt;math&amp;gt;m&amp;lt;k&amp;lt;/math&amp;gt;, wäre die Lösung der Strategie besser als die optimale.&lt;br /&gt;
:* Angenommen &amp;lt;math&amp;gt;m&amp;gt;k&amp;lt;/math&amp;gt;, dann enthält &amp;lt;math&amp;gt;O&amp;lt;/math&amp;gt; eine Aktivität &amp;lt;math&amp;gt;j_{k+1}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
:Nach Schritt 1 gilt:&lt;br /&gt;
::&amp;lt;math&amp;gt;f(i_k)\leq f(j_k)\leq f(j_{k+1})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:Wegen Kompatibilität gilt aber:&lt;br /&gt;
::&amp;lt;math&amp;gt;s(j_{k+1})\geq f(j_k)\geq f(i_k)&amp;lt;/math&amp;gt;&lt;br /&gt;
:-&amp;gt; Die Greedy Strategie hätte also noch die Aktivität &amp;lt;math&amp;gt;j_{k+1}&amp;lt;/math&amp;gt; wählen können.&lt;br /&gt;
:-&amp;gt; Widerspruch zur Annahme, dass die Greedy Strategie durchgelaufen ist, bis keine Aktivität mehr hinzugefügt werden kann&lt;br /&gt;
:-&amp;gt; m&amp;gt;k ist falsch&lt;br /&gt;
:-&amp;gt; m=k ist richtig&lt;br /&gt;
&lt;br /&gt;
=== Beispiel zur Dynamischen Programmierung: Weighted Intervall Scheduling ===&lt;br /&gt;
:Die Problemstellung ähnelt dem des normalen Intervall Scheduling, hier haben die Aktivitäten aber Gewichte &amp;lt;math&amp;gt;w_i&amp;lt;/math&amp;gt;&lt;br /&gt;
:(z.B. Bringt eine längere Aufgabe in einem Übunsgzettel in der Regel auch mehr Punkte, d.h. sie hat eine hohe Gewichtung)&lt;br /&gt;
==== Ziel ====&lt;br /&gt;
:Wähle die Aktivitäten so, dass der Gewinn (Summe der Gewichtungen der bearbeiteten Aktivitäten) maximal wird.&lt;br /&gt;
==== Ansatz ====&lt;br /&gt;
:* Sortiere Aktivitäten nach ihrer Endzeit.&lt;br /&gt;
:* Definiere eine Funktion &amp;lt;math&amp;gt;p(i)&amp;lt;/math&amp;gt;, welche für die Aktivität steht, die vor &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt; endet, mit &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt; kompatibel ist, unter allen Aktivitäten mit diesen Eigenschaften die letzte (d.h. mit der spätesten Endzeit) ist. &lt;br /&gt;
&lt;br /&gt;
In folgendem Beispiel wird die Aktivität &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt; mit der Symbolik |-!-| betrachtet um deren p-Funktion zu evaluieren.&lt;br /&gt;
&lt;br /&gt;
Für die p-Funktion kommen lediglich die Funktionen mit der Symbolik |===| und |====| in Frage, die untere der beiden ist die gesuchte Aktivität.&lt;br /&gt;
    |====|        |-!-| |--|&lt;br /&gt;
        |===| |-----|         |----|&lt;br /&gt;
&lt;br /&gt;
:Trivial ist, dass &amp;lt;math&amp;gt;a_n&amp;lt;/math&amp;gt; entweder zur Lösung gehört, oder nicht:&lt;br /&gt;
&lt;br /&gt;
[[Image:wis1.JPG]]&lt;br /&gt;
&lt;br /&gt;
:Dadurch ergibt sich folgende Funktion:&lt;br /&gt;
:&amp;lt;math&amp;gt;OPT(n)=\begin{cases}&lt;br /&gt;
\ \;\, \{a_n\}\cup OPT(p(n))\\&lt;br /&gt;
\ \;\, OPT(a_{n-1})&lt;br /&gt;
\end{cases}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:Um den höchstmöglichen Gewinn zu erzielen, wird &amp;lt;math&amp;gt;\{a_n\}\cup OPT(p(n))&amp;lt;/math&amp;gt; verwendet falls gilt:&lt;br /&gt;
:&amp;lt;math&amp;gt;w_n + Gewinn(OPT(p(n))\leq Gewinn(OPT(n-1))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:Ansonsten wird &amp;lt;math&amp;gt;OPT(a_{n-1})&amp;lt;/math&amp;gt; angewandt.&lt;br /&gt;
&lt;br /&gt;
== Erinnerung: Intervalle ==&lt;br /&gt;
&lt;br /&gt;
[[Image:bild1.JPG]]&lt;br /&gt;
&lt;br /&gt;
Sortiere Intervalle nach aufsteigender &amp;lt;math&amp;gt; f_{i} &amp;lt;/math&amp;gt;:&lt;br /&gt;
:Gewinn &amp;lt;math&amp;gt;(a_{n})&amp;lt;/math&amp;gt; = max &amp;lt;math&amp;gt;(w_{n})&amp;lt;/math&amp;gt; + Gewinn (p(n)), Gewinn &amp;lt;math&amp;gt; (a_{n-1})&amp;lt;/math&amp;gt;&lt;br /&gt;
:Gewinn &amp;lt;math&amp;gt; (a_{n-1})&amp;lt;/math&amp;gt; = max (&amp;lt;math&amp;gt;w_{n-1}&amp;lt;/math&amp;gt; + Gewinn &amp;lt;math&amp;gt;(p(n-1)&amp;lt;/math&amp;gt;), Gewinn &amp;lt;math&amp;gt; (a_{n-2})&amp;lt;/math&amp;gt;&lt;br /&gt;
:usw.&lt;br /&gt;
&lt;br /&gt;
[[Image:bild2.JPG]]&lt;br /&gt;
&lt;br /&gt;
Bearbeite Teilprobleme in Reihenfolge der Sortierung&lt;br /&gt;
:=&amp;gt; azyklischer Graph&lt;br /&gt;
:(a2 hängt von a1 ab, a3 von a2, a4 von a2 und a3 usw.)&lt;br /&gt;
&lt;br /&gt;
Wie erkennt man, ob der Graph azyklisch ist, und wie fndet man die Reihenfolge?&lt;br /&gt;
* azyklischer, gerichteter Graph: „directed acyclic graph“ – DAG&lt;br /&gt;
* Ein Graph ist genau dann ein DAG, wenn es eine topologische Sortierung der Knoten gibt.&lt;br /&gt;
:Def.: Zeichne die Knoten so auf eine Gerade, dass alle Kanten nach rechts/in dieselbe Richtung zeigen&lt;br /&gt;
&lt;br /&gt;
[[Image:bild3.JPG]]&lt;br /&gt;
&lt;br /&gt;
:=&amp;gt; arbeite topologische Sortierung von rechts nach links ab.&lt;br /&gt;
&lt;br /&gt;
=== Beispiel ===&lt;br /&gt;
Wie erklärt man einem zerstreuten Professor, wie er sich morgens anziehen soll?&lt;br /&gt;
&lt;br /&gt;
[[Image:bild4.JPG]]&lt;br /&gt;
&lt;br /&gt;
:=&amp;gt; Die topologische Sortierung ist hier nicht eindeutig, z.B. ist nicht klar, ob zuerst die Strümpfe angezogen werden sollen oder die Unterhose. Wann die Uhr angelegt werden soll, ist überhaupt nicht festgelegt. Mit dieser Beschreibung käme der arme Professor wohl kaum zurecht.&lt;br /&gt;
&lt;br /&gt;
[[Image:bild5.JPG]]&lt;br /&gt;
&lt;br /&gt;
== Zwei Algorithmen zum Finden der topologischen Sortierung ==&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 vorhandenen Knoten (also 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;
Bild: Ein zyklischer Graph&lt;br /&gt;
&lt;br /&gt;
=== Algorithmus 2 ===&lt;br /&gt;
Verwende Tiefensuche, um die Finishing Time zu bestimmen&lt;br /&gt;
: =&amp;gt; zeichne Knoten nach abnehmender Finishing Time auf die Gerade&lt;br /&gt;
&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;
[[Image:bild7.JPG]]&lt;br /&gt;
&lt;br /&gt;
Fälle:&lt;br /&gt;
# Matche a[i] mit b[j]. Falls a[i] == b[j], ist das gut. Andernfalls entstehen Kosten &amp;lt;math&amp;gt;K_{a[i], b[j]}&amp;lt;/math&amp;gt;&lt;br /&gt;
# Wir überspringen a[i] oder b[j], =&amp;gt; Kosten L&lt;br /&gt;
&lt;br /&gt;
:gesucht: Alignment mit minimalen Kosten&lt;br /&gt;
&lt;br /&gt;
[[Image:bild8.JPG]]&lt;br /&gt;
&lt;br /&gt;
Lösung:&lt;br /&gt;
:Suche kürzesten Pfad von links oben nach rechts unten (z.B. mit dem [[Graphen und Graphenalgorithmen#Algorithmus von Dijkstra|Algorithmus von Dijkstra]])&lt;br /&gt;
:In unserem Beispiel von oben:&lt;br /&gt;
&lt;br /&gt;
[[Image:bild9.JPG]]&lt;br /&gt;
&lt;br /&gt;
=== Problemlösung durch einen Entscheidungsbaum ===&lt;br /&gt;
Greedy Algorithm und dynamische Programmierung: &lt;br /&gt;
Transformationen des Problems, sodass '''nicht''' der ganze Entscheidungsbaum durchlaufen werden muss.&lt;br /&gt;
=&amp;gt; effizient&lt;br /&gt;
&lt;br /&gt;
* bei vielen Problemen ist keine Möglichkeit bekannt, das vollständige Durchlaufen des Entscheidungsbaumes zu vermeiden, z.B. [[Graphen_und_Graphenalgorithmen#Problem_des_Handlungsreisenden | Problem des Handlungsreisenden]] (TSP), 3-SAT&lt;br /&gt;
* Frage: Gibt es prinzipiell keinen effizienten Algorithmus, oder sind wir nur zu blöd?&lt;br /&gt;
* Derzeitiger Stand: viele dieser Probleme sind fundamental äquivalent „NP complete problems“ – „NP vollständig“ (NP = &amp;quot;nicht-deterministisch polynomiell&amp;quot;)&lt;/div&gt;</summary>
		<author><name>Jschleic</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Randomisierte_Algorithmen&amp;diff=2530</id>
		<title>Randomisierte Algorithmen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Randomisierte_Algorithmen&amp;diff=2530"/>
				<updated>2008-07-22T10:35:52Z</updated>
		
		<summary type="html">&lt;p&gt;Jschleic: /* Mersenne Twister */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 1. Randomisierte Algorithmen ==&lt;br /&gt;
&lt;br /&gt;
'''Def.:''' Algorithmen, die bei Entscheidung oder bei der Wahl der Parameter Zufallszahlen benutzen&lt;br /&gt;
&lt;br /&gt;
'''Bsp.:''' Lösen des K-SAT-Problems durch RA&lt;br /&gt;
    geg.: logischer Ausdruck in K-CNF (n Variablen, m Klauseln, k Variablen pro Klausel)&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;math&amp;gt;\underbrace {\underbrace {\left(x_1 \vee x_3 \vee...\right)}_{k\; Variablen} \wedge \left( x_2 \vee x_4 \vee...\right)}_{m\;Klauseln}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    for i in range (trials):    #Anzahl der Versuche&lt;br /&gt;
         #Bestimme eine Zufallsbelegung des &amp;lt;math&amp;gt;\{ x_i \}&amp;lt;/math&amp;gt;:&lt;br /&gt;
         for j in range (steps):&lt;br /&gt;
               if &amp;lt;math&amp;gt;\{ x_i \}&amp;lt;/math&amp;gt; erfüllt alle Klauseln: return &amp;lt;math&amp;gt;\{ x_i \}&amp;lt;/math&amp;gt;&lt;br /&gt;
               #wähle zufällig eine Klausel, die nicht erfüllt ist und negiere zufällig eine der Variablen in dieser Klausel &lt;br /&gt;
               (die Klausel ist jetzt erfüllt)&lt;br /&gt;
    return None&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Eigenschaft: falls &amp;lt;math&amp;gt;k&amp;gt;2&amp;lt;/math&amp;gt; : steps *trials &amp;lt;math&amp;gt;\in O\left(\Alpha^n \right) \Alpha &amp;gt;1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
z.B. &amp;lt;math&amp;gt;k=3&amp;lt;/math&amp;gt; steps=3*n, trials=&amp;lt;math&amp;gt;\left(\frac{4}3\right)^n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
aber: bei &amp;lt;math&amp;gt;k=2&amp;lt;/math&amp;gt; sind im Mittel nur steps=&amp;lt;math&amp;gt;O\left(n^2\right)&amp;lt;/math&amp;gt; nötig, trials=&amp;lt;math&amp;gt;O\left(1\right)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''-Zufallsbelegung hat  &amp;lt;math&amp;gt;t\leq n&amp;lt;/math&amp;gt; richtige Variablen (im Mittel &amp;lt;math&amp;gt;t\approx \frac {n} 2&amp;lt;/math&amp;gt;)'''&lt;br /&gt;
&lt;br /&gt;
Negieren einer Variable ändert t um 1,&lt;br /&gt;
u.Z. &amp;lt;math&amp;gt;t\rightarrow t+1&amp;lt;/math&amp;gt; mit Wahrscheinlichkeit &amp;lt;math&amp;gt;\frac 1 2&amp;lt;/math&amp;gt; ::(für beliebiges k: &amp;lt;math&amp;gt;\frac 1 k&amp;lt;/math&amp;gt;)&lt;br /&gt;
::::::::::&amp;lt;math&amp;gt;t\rightarrow t-1&amp;lt;/math&amp;gt; mit Wahrscheinlichkeit &amp;lt;math&amp;gt;\frac 1 2&amp;lt;/math&amp;gt; ::(für beliebiges k: &amp;lt;math&amp;gt;\frac {k-1} k&amp;lt;/math&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''-Wieviele Schritte braucht man im Mittel, um zu einer Lösung mit t Richtigen zu kommen?'''&lt;br /&gt;
&lt;br /&gt;
       &amp;lt;math&amp;gt;S\left(t\right)=\frac 1 2 S\left(t-1\right) + \frac 1 2 S\left(t+1\right) +1&amp;lt;/math&amp;gt;&lt;br /&gt;
       &lt;br /&gt;
       &amp;lt;math&amp;gt;S\left(n\right)=0&amp;lt;/math&amp;gt;    #Abbruchbedingung der Schleife&lt;br /&gt;
       &lt;br /&gt;
       &amp;lt;math&amp;gt;S\left(0\right) = S\left( 1\right) + 1 \Longrightarrow S\left(t\right) = n^2-t^2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
       '''Probe:''' &amp;lt;math&amp;gt;S\left(n\right)=n^2-n^2=0&amp;lt;/math&amp;gt; &lt;br /&gt;
                  &lt;br /&gt;
              &amp;lt;math&amp;gt;S\left(0\right) =n^2-0^2&amp;lt;/math&amp;gt;  &lt;br /&gt;
              &lt;br /&gt;
                   &amp;lt;math&amp;gt;=S\left(1\right)+1&amp;lt;/math&amp;gt;&lt;br /&gt;
              &lt;br /&gt;
                   &amp;lt;math&amp;gt;\;=n^2-1^2+1&amp;lt;/math&amp;gt;&lt;br /&gt;
              &lt;br /&gt;
                   &amp;lt;math&amp;gt;\;=n^2&amp;lt;/math&amp;gt;&lt;br /&gt;
              &amp;lt;math&amp;gt;S\left(t\right)=\frac 1 2 \left(n^2-\left(t-1\right)^2\right) + \frac 1 2 \left(n^2-\left(t+1\right)^2\right)+1&amp;lt;/math&amp;gt; &lt;br /&gt;
              &lt;br /&gt;
                   &amp;lt;math&amp;gt;=\frac 1 2 n^2-\frac 1 2 \left( t^2-2t+1\right) + \frac 1 2 n^2-\frac 1 2&amp;lt;/math&amp;gt;&lt;br /&gt;
              &lt;br /&gt;
                   &amp;lt;math&amp;gt;=\left(t^2+2t+1\right)&amp;lt;/math&amp;gt;              &lt;br /&gt;
              &lt;br /&gt;
                   &amp;lt;math&amp;gt;\;=n^2-t^2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Das ist das Random Walk Problem'''&lt;br /&gt;
&lt;br /&gt;
Im ungünstigsten Fall (t=0) werden im Mittel &amp;lt;math&amp;gt;n^2&amp;lt;/math&amp;gt; Schritte benötigt, um durch random walk nach t=n zu gelangen.&lt;br /&gt;
&lt;br /&gt;
== 2. RANSAC-ALGORITHMUS (Random Sample Consensus)==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;u&amp;gt;''Aufgabe:''&amp;lt;/u&amp;gt; gegeben: Datenpunkte&lt;br /&gt;
::gesucht: Modell, das die Datenpunkte erklärt&lt;br /&gt;
&lt;br /&gt;
[[Image:Rubto.png|thumb|250px|none]]&lt;br /&gt;
&lt;br /&gt;
'''Messpunkte:'''&lt;br /&gt;
   &lt;br /&gt;
      übliche Lösung: Methode der kleinsten Quadrate&lt;br /&gt;
      &lt;br /&gt;
      &amp;lt;math&amp;gt;\min_{a,b} 	\sum_{i} \left(a x_i + b + y_i\right)^2&amp;lt;/math&amp;gt;&lt;br /&gt;
      &lt;br /&gt;
      Schulmathematik:      &amp;lt;math&amp;gt;Minimum\stackrel{\wedge}{=}Ableitung=0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Lineares Gleichungssystem'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\frac{d}{da}\sum{i} \left(ax_i+b-y_i\right)^2=\sum{i} \frac{d}{da} \left[ax_i+b-y_i\right)^2&amp;lt;/math&amp;gt;  &lt;br /&gt;
&lt;br /&gt;
::::&amp;lt;math&amp;gt;f\left(g\left(x\right)\right)&amp;lt;/math&amp;gt;   &lt;br /&gt;
&lt;br /&gt;
::::&amp;lt;math&amp;gt;f\left(x\right)=x^2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
::::&amp;lt;math&amp;gt;y\left(a\right)=ax_i+b-y_i&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;=\sum_{i}2\left(ax_i+b-y_i\right)\frac{d}{da} \underbrace {ax_i+b-y_i}_{x_i}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\underline {=2\sum_{i}\left(ax_i+b-y_i\right)x_i\stackrel{!}{=}0}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
::::::&amp;lt;math&amp;gt;a\sum_{i}{x_i}^2+b\sum_{i}x_i=\sum_{i}x_iy_i&amp;lt;/math&amp;gt;   &lt;br /&gt;
&lt;br /&gt;
::::::&amp;lt;math&amp;gt;a\sum_{i}x_i+b\sum_{i}1=\sum_{i}y_i&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\frac{d}{db}\sum_{i}\left(ax_i+b-y_i\right)^2=2\sum_{i}\left(ax_i+b-y_i\right)*1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
:Problem: &amp;lt;math&amp;gt;\epsilon  %&amp;lt;/math&amp;gt; der Datenpunkte sind Outlier&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\Longrightarrow&amp;lt;/math&amp;gt; Einfaches Anpassen des Modells an die Datenpunkte funktioniert nicht&lt;br /&gt;
&lt;br /&gt;
:Seien mindestens k Datenpunkte notwendig, um das Programm anpassen zu können&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
RANSAC-Algorithmus&lt;br /&gt;
&lt;br /&gt;
      for  l in range (trials):&lt;br /&gt;
           wähle zufällig k Punkte aus&lt;br /&gt;
           passe das Modell an die k Punkte an&lt;br /&gt;
           zähle, wieviele Punkte in der Nähe des Modells liegen (d.h. &amp;lt;math&amp;gt;d_i &amp;lt; d_max&amp;lt;/math&amp;gt; muss geschickt gewählt werden) &lt;br /&gt;
                                           #Bsp. Geradenfinden:-wähle a,b aus zwei Punkten&lt;br /&gt;
                                                               -berechne: &amp;lt;math&amp;gt;|ax_i+b-y_i|=d_i&amp;lt;/math&amp;gt;&lt;br /&gt;
                                                               -zähle Punkt i als Inlier, falls &amp;lt;math&amp;gt;d_i&amp;lt;d_ma&amp;lt;/math&amp;gt;&lt;br /&gt;
      return: Modell mit höchster Zahl der Inlier&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      &amp;lt;math&amp;gt;trials= \frac{log\left(1-p\right)}{log\left(1-\left(1-\epsilon\right)^k\right)}&amp;lt;/math&amp;gt;  mit k=Anzahl der Datenpunkte und p=Erfolgswahrscheinlichkeit, &amp;lt;math&amp;gt;\epsilon&amp;lt;/math&amp;gt;=Outlier-Anteil&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Erfolgswahrscheinlichkeit: p=99%'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\begin{array}{|c||c|c|c|c|c|}&lt;br /&gt;
         Beispiel &amp;amp; k &amp;amp; \epsilon=10% &amp;amp; 20% &amp;amp; 50% &amp;amp; 70%\\&lt;br /&gt;
         \hline&lt;br /&gt;
         Linie\;in\;2D &amp;amp; 2 &amp;amp; 3 &amp;amp;5 &amp;amp; 17 &amp;amp; 49\\&lt;br /&gt;
         Kreis\;in\;2D &amp;amp; 3 &amp;amp; 4 &amp;amp; 7 &amp;amp; 35 &amp;amp; 169\\&lt;br /&gt;
         Ebene\;in\;3D &amp;amp; 8 &amp;amp; 9 &amp;amp; 26 &amp;amp; 1172 &amp;amp; 70188\\&lt;br /&gt;
       \end{array}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Ein Spiel: Wie viel Schritte braucht man im Mittel zum Ziel?'''&lt;br /&gt;
&lt;br /&gt;
   geg.: 5 Plätze, 2 Personen: eine Person rückt vom einem Platz zu dem enderen Platz;&lt;br /&gt;
         die zweite Person wirft die Münze.&lt;br /&gt;
         Wenn die Münze auf Kopf landet, rücke nach rechts und wenn die Münze auf Zahl landet, rücke nach links.&lt;br /&gt;
         &amp;lt;--- Zahl                                                         Kopf--&amp;gt;&lt;br /&gt;
         Kopf: /////&lt;br /&gt;
         Zahl: /// &lt;br /&gt;
&lt;br /&gt;
:: =&amp;gt; mit 8 Schritten bis zum Ziel&lt;br /&gt;
:im Mittel: bei N Plätzen braucht man N&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; Schritte&lt;br /&gt;
&lt;br /&gt;
: all: mit N&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; Schritten um N Plätze rücken&lt;br /&gt;
: Wie viel Schritte braucht man im Mittel zum Ziel?&lt;br /&gt;
&lt;br /&gt;
         &amp;lt;math&amp;gt;S\left(N\right)=0&amp;lt;/math&amp;gt;    #wenn wir uns im Stuhl Nr.1 befinden&lt;br /&gt;
           &lt;br /&gt;
         &amp;lt;math&amp;gt;S\left(i\right)=\frac 1 2 S\left(1 + S\left(i+1\right)\right) + \frac 1 2 S\left(1 + S\left(i-1\right)\right) = \frac 1 2 S\left(i+1\right) + \frac 1 2 S\left(i-1\right) +1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
         &amp;lt;math&amp;gt;S\left(0\right)=1 + S\left(1\right)&amp;lt;/math&amp;gt;    #bei 0.Platz&lt;br /&gt;
&lt;br /&gt;
:::*Lösung: &lt;br /&gt;
&lt;br /&gt;
         &amp;lt;math&amp;gt;S\left(i\right)= N^2 - i^2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:::*speziell: &lt;br /&gt;
&lt;br /&gt;
         &amp;lt;math&amp;gt;S\left(i\right)= N^2&amp;lt;/math&amp;gt;           #wenn man am ungünstigsten Platz startet&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
'''Beziehung zu randomisiertem 2-SAT'''&lt;br /&gt;
&lt;br /&gt;
      &amp;quot;Platz &amp;lt;math&amp;gt;i&amp;lt;/math&amp;gt; &amp;quot;: &amp;lt;math&amp;gt;i&amp;lt;/math&amp;gt; Variablen haben den richtigen Wert,  &amp;lt;math&amp;gt;\left(N-i\right)&amp;lt;/math&amp;gt;  sind falsch gesetzt&lt;br /&gt;
&lt;br /&gt;
      &amp;lt;math&amp;gt;S\left(\frac N 2\right)=N^2 - \left(\frac N 2\right)^2 = N^2 - \frac N 4 ^2 = \frac 3 4 N^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
      &amp;lt;math&amp;gt;S\left(\frac N 2\right)&amp;lt;/math&amp;gt;     # Anfangszustand&lt;br /&gt;
----&lt;br /&gt;
== '''Las Vegas vs. Monte Carlo'''==&lt;br /&gt;
&lt;br /&gt;
   * ''Las Vegas - Algorithmen''&lt;br /&gt;
     - Ergebnis ist immer korrekt.&lt;br /&gt;
     - Berechnung ist mit hoher Wahrscheinlichkeit effizient (d.h. Randomisierung macht den ungünstigsten Fall unwahrscheinlich).&lt;br /&gt;
&lt;br /&gt;
   * ''Monte Carlo - Algorithmen''&lt;br /&gt;
     - Berechnung immer effizient.&lt;br /&gt;
     - Ergebnis mit hoher Wahrscheinlichkeit korrekt (falls kein effizienter Algorithmus bekannt, der immer die richtige Lösung liefert).&lt;br /&gt;
&lt;br /&gt;
{| border = &amp;quot;1&amp;quot; cellspacing=&amp;quot;0&amp;quot; cellpadding=&amp;quot;5&amp;quot; align=&amp;quot;center&amp;quot;&lt;br /&gt;
|- &lt;br /&gt;
! Las Vegas&lt;br /&gt;
! Monte Carlo&lt;br /&gt;
|- &lt;br /&gt;
| - Erzeugen einer perfekten Hashfuktion &lt;br /&gt;
| - Algorithmus von Freiwald(Matrizenmultiplikation)&lt;br /&gt;
|-&lt;br /&gt;
| - universelles Hashing&lt;br /&gt;
| - RANSAC&lt;br /&gt;
|-&lt;br /&gt;
| - Quick Sort mit zufälliger Wahl des Pivot-Elements&lt;br /&gt;
| - randomisierte K-SAT(k&amp;gt;=3)(Alg. von Schöning)&lt;br /&gt;
|-&lt;br /&gt;
| - Treep mit zufälligen Prioritäten&lt;br /&gt;
| -&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== ''' Zufallszahlen ''' ==&lt;br /&gt;
&lt;br /&gt;
:- kann man nicht mit deterministischen Computern erzeugen&lt;br /&gt;
:- aber man kann Pseudo-Zufallszahlen erzeugen, die viele Eigenschaften von echten Zufallszahlen haben&lt;br /&gt;
::: * sehr ähnlich  zum Hash&lt;br /&gt;
&lt;br /&gt;
     ''&amp;quot;linear Conguential Random number generator&amp;quot;''&lt;br /&gt;
        &amp;lt;math&amp;gt;I_{i+1}= \left(a*I_i + c\right)mod m&amp;lt;/math&amp;gt;&lt;br /&gt;
        &amp;lt;math&amp;gt;\begin{array}{ll}&lt;br /&gt;
        \mathrm{=&amp;gt; } &amp;amp; I_i \in [0, m-1]\\&lt;br /&gt;
&lt;br /&gt;
        \end{array}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
:-sorgfältige Wahl von  a, c, m notwendig&lt;br /&gt;
::'''Bsp.'''  m = 2&amp;lt;sup&amp;gt;32&amp;lt;/sup&amp;gt;&lt;br /&gt;
::: a = 1664525, c = 1013904223&lt;br /&gt;
::: ''&amp;quot;quick and dirty generator&amp;quot;''&lt;br /&gt;
&lt;br /&gt;
==='''Nachteile'''===&lt;br /&gt;
&lt;br /&gt;
* nicht zufällig genug für viele Anwendungen&lt;br /&gt;
::'''Bsp.''' wähle Punkt in R&amp;lt;sup&amp;gt;3&amp;lt;/sup&amp;gt;&lt;br /&gt;
&lt;br /&gt;
::&amp;lt;math&amp;gt;\begin{array}{ll}&lt;br /&gt;
      \mathrm{ } &amp;amp; p = (rand(), rand(), rand())\\&lt;br /&gt;
&lt;br /&gt;
      \end{array}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
::gibt Zahl u, v, w so, dass &lt;br /&gt;
&lt;br /&gt;
::&amp;lt;math&amp;gt;\begin{array}{ll}&lt;br /&gt;
        \mathrm{ } &amp;amp; u * p[0] + v * p[1] + w * p[3]\\&lt;br /&gt;
&lt;br /&gt;
        \end{array}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
::stark geclustert ist.&lt;br /&gt;
&lt;br /&gt;
* Periodenlänge ist zu kurz:&lt;br /&gt;
:: spätestens nach m Schritten wiederholt sich die Folge&lt;br /&gt;
&lt;br /&gt;
::'''allgemein''': falls der interne Zustand des Zufallsgenerators ''k'' bits hat, ist Periodenlänge:&lt;br /&gt;
&lt;br /&gt;
::&amp;lt;math&amp;gt;\begin{array}{ll}&lt;br /&gt;
        \mathrm{ } &amp;amp; Periode &amp;lt; 2^k\\&lt;br /&gt;
&lt;br /&gt;
        \end{array}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* ''lowbits'' sind weniger zufällig als die ''highbits''&lt;br /&gt;
----&lt;br /&gt;
=== ''Mersenne Twister''===&lt;br /&gt;
   &lt;br /&gt;
&lt;br /&gt;
'''bester zur Zeit bekannter Zufallszahlengenerator (ZZG)'''&lt;br /&gt;
* innere Zustand: &amp;lt;math&amp;gt;\begin{array}{ll}&lt;br /&gt;
        \mathrm{ } &amp;amp; 624*32 bit\ Integers  =&amp;gt; 19968 bits\\&lt;br /&gt;
        \end{array}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Periodenlänge: &amp;lt;math&amp;gt;2^ {19937} \approx 4 * 10^{6000}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Punkte aus aufeinanderfolgende Zufallszahlen in &amp;lt;math&amp;gt;\mathbb{R}^n&amp;lt;/math&amp;gt; sind gleich verteilt bis &amp;lt;math&amp;gt;\begin{array}{ll}&lt;br /&gt;
        \mathrm{ } &amp;amp; n = 623\\&lt;br /&gt;
        \end{array}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* alle Bits sind unabhängig voneinander zufällig (&amp;quot;Twister&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
* schnell&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  class Random:&lt;br /&gt;
    def __init__(self, seed):&lt;br /&gt;
        self.N = 624&lt;br /&gt;
        self.state = [0]*624&lt;br /&gt;
        self.state = zufällig mit Hilfe des ''seeds'' initialisieren (einfacher ZZG)&lt;br /&gt;
        self.i = 0    # zählt mit in welchem Zustand wir gerade aufhalten&lt;br /&gt;
 &lt;br /&gt;
    def __call__(self):&lt;br /&gt;
        N,M = 624, 397&lt;br /&gt;
        i = self.i&lt;br /&gt;
        r = (self.state[i] &amp;amp; 0x80000000)|(self.state[(i+1)%N] &amp;amp; 0x7FFFFFFF)     # aktualisieren&lt;br /&gt;
        if self.state[(i+1)%N]&amp;amp;1:                                               # des Zustands&lt;br /&gt;
           r^= 0x9908B0DF&lt;br /&gt;
        self.state[i] = self.state[(i+1)%N]*^r&lt;br /&gt;
 &lt;br /&gt;
        y = self.state[i]&lt;br /&gt;
           self.i = (self.i + 1)%N&lt;br /&gt;
           # bits verwürfeln&lt;br /&gt;
           y ^= (y&amp;gt;&amp;gt;11)&lt;br /&gt;
           y ^= ((y&amp;gt;&amp;gt;7) &amp;amp; 0x9D2C5680)&lt;br /&gt;
           y ^= ((y&amp;gt;&amp;gt;15) &amp;amp; 0xEFC60000)&lt;br /&gt;
           y ^= (y&amp;gt;&amp;gt;18)&lt;br /&gt;
         return y&lt;br /&gt;
&lt;br /&gt;
'''geg.:''' Zufallszahl &lt;br /&gt;
&amp;lt;math&amp;gt;\begin{array}{ll}&lt;br /&gt;
        \mathrm{ } &amp;amp; [0, \overbrace{2^{32}-1}^{m-1}]\\&lt;br /&gt;
        \end{array}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''ges.:''' Zufallszahl&lt;br /&gt;
&amp;lt;math&amp;gt;\begin{array}{ll}&lt;br /&gt;
        \mathrm{ } &amp;amp; [0, k - 1]\\&lt;br /&gt;
        \end{array}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''naive Lösung:'''  &amp;lt;math&amp;gt;\begin{array}{ll}&lt;br /&gt;
        \mathrm{ } &amp;amp; rand()%k\\&lt;br /&gt;
        \end{array}&amp;lt;/math&amp;gt;  ist schlecht.&lt;br /&gt;
&lt;br /&gt;
'''Bsp.'''&lt;br /&gt;
&amp;lt;math&amp;gt;\begin{array}{ll}&lt;br /&gt;
        \mathrm{ } &amp;amp; \qquad m = 16\qquad k = 11\\&lt;br /&gt;
        \end{array}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;0&amp;quot; cellpadding=&amp;quot;5&amp;quot; &lt;br /&gt;
! rand() || 0 || 1 || 2 || 3 || 4 || 5 || 6 || 7 || 8 || 9 || 10 || 11 || 12 || 13 || 14 || 15&lt;br /&gt;
|-&lt;br /&gt;
! rand()%k&lt;br /&gt;
! 0 || 1 || 2 || 3 || 4 || 5 || 6 || 7 || 8 || 9 || 10 || 0 || 1 || 2 || 3 || 4 &lt;br /&gt;
|-&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=&amp;gt; 0,...,n kommt doppelt so häufig wie 5,...,10 &amp;quot;nicht zufällig&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Lösung:'''  Zurückweisen des Rests der Zahlen (''rejektion sampling'')&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\begin{array}{ll}&lt;br /&gt;
        \mathrm{ } &amp;amp; remainder = (m - 1 - (k - 1))% k = (m - k)%k\\&lt;br /&gt;
        \mathrm{ } &amp;amp; last\ Good\ Value = m-1-remainder\\&lt;br /&gt;
        \end{array}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
  r = rand()&lt;br /&gt;
  while r &amp;gt; last.GoodValue:&lt;br /&gt;
        r = rand()&lt;br /&gt;
        return r%k&lt;/div&gt;</summary>
		<author><name>Jschleic</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Container&amp;diff=2528</id>
		<title>Container</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Container&amp;diff=2528"/>
				<updated>2008-07-22T09:26:13Z</updated>
		
		<summary type="html">&lt;p&gt;Jschleic: /* Beispiele */ Gliederung&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;der Begriff '''Container''' stammt ursprünglich aus der Schiffahrt,&lt;br /&gt;
Container kann mehrere Bedeutungen annehmen.&lt;br /&gt;
 &lt;br /&gt;
'''in der Schifffahrt''': &amp;lt;br/&amp;gt;&lt;br /&gt;
Behälter der Güter lagern und transportieren kann.&lt;br /&gt;
&lt;br /&gt;
'''in der Datenverarbeitung''': &amp;lt;br/&amp;gt; &lt;br /&gt;
- '''Computertechnik''': eine Dateiformat die verschiedenartige Datenformate enthalten kann. (siehe [http://de.wikipedia.org/wiki/Containerformat Containerformat]) &amp;lt;br/&amp;gt;&lt;br /&gt;
- '''Server''': Teil einer Server-Software und Enterprise Java Beans (EJB) verwaltet. Der Container speichert die Daten und prüft die Verfügbarkeit für jeden autorisierten Client. (siehe [http://de.wikipedia.org/wiki/EJB-Container EJB-Container]) &amp;lt;br/&amp;gt;&lt;br /&gt;
- '''in Programmiersprachen''': Einige Programmiersprachen wie Java oder C++ verfügen über Containerklassen. In C++ sind sie in der Stardbibliothek [http://de.wikipedia.org/wiki/C%2B%2B-Standardbibliothek C++Standardbibliothek] &amp;lt;br/&amp;gt;&lt;br /&gt;
- '''in der Informatik''' (und bei uns in der Vorlesung): ein abstraktes Objekt, das Elemente des gleichen Typs speichert (Array, Liste, Dictionary,...) siehe auch [http://de.wikipedia.org/wiki/Datenstruktur Datenstrukturen]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Mögliche Operationen==&lt;br /&gt;
&lt;br /&gt;
Welche Operationen hätte man denn gerne bei einer solchen Container-Datenstruktur?&lt;br /&gt;
Was benötigen Algorithmen häufig? Wie sollten die Daten organisiert sein, damit Algorithmen effizient damit arbeiten können? &amp;lt;br/&amp;gt;&lt;br /&gt;
Eine solche Anforderungsanalyse ist sehr aufwendig und kann sich über Jahre erstrecken, weil Erfahrungen gesammelt werden müssen, welche Anforderungen an Datenstrukturen in vielen Algorithmen immer wieder auftreten. &amp;lt;br/&amp;gt;&lt;br /&gt;
Wir listen im folgenden nur das Resultat, also die wichtigsten Operationen von Container-Datenstrukturen auf.&lt;br /&gt;
  &lt;br /&gt;
Sei &amp;lt;tt&amp;gt;c&amp;lt;/tt&amp;gt; eine Container-Datenstruktur und &amp;lt;tt&amp;gt;v&amp;lt;/tt&amp;gt; ein darin gespeicherter Wert:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
===Lesender Zugriff===&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;0&amp;quot; cellpadding=&amp;quot;7&amp;quot; &lt;br /&gt;
|-valign=&amp;quot;top&amp;quot; &lt;br /&gt;
|'''0.'''&lt;br /&gt;
| &amp;lt;tt&amp;gt;c.size()&amp;lt;/tt&amp;gt;&lt;br /&gt;
|gibt die Anzahl der Elemente im Container an&lt;br /&gt;
|-&lt;br /&gt;
|'''1a.'''&lt;br /&gt;
|&amp;lt;tt&amp;gt;v = c.get(i)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|das i-te Element im Container lesen&lt;br /&gt;
|-&lt;br /&gt;
|'''1b.'''&lt;br /&gt;
|&amp;lt;tt&amp;gt;v = c.get(pos)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|das Element an Position &amp;lt;tt&amp;gt;pos&amp;lt;/tt&amp;gt; lesen (&amp;lt;tt&amp;gt;pos&amp;lt;/tt&amp;gt; ist ein geeignetes Hilfsobjekt, das in Abhängigkeit von der Art der Datenstruktur eine Position im Container referenziert. Im Falle 1a. &amp;lt;tt&amp;gt;v = c.get(i)&amp;lt;/tt&amp;gt; ist &amp;lt;tt&amp;gt;pos&amp;lt;/tt&amp;gt; eine natürliche Zahl, aber es gibt auch andere Möglichkeiten, die Position zu kodieren.)&lt;br /&gt;
|-&lt;br /&gt;
|'''1c.'''&lt;br /&gt;
|&amp;lt;tt&amp;gt;v = c.get(key)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|das Element mit dem Schlüssel &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; lesen (Beachte den Unterschied zu 1b: In 1b markiert &amp;lt;tt&amp;gt;pos&amp;lt;/tt&amp;gt; eine Position im Container, hier in 1c bezieht sich &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; auf eine Eigenschaft der Datenelemente, die von der Position im Container unabhängig ist.)&lt;br /&gt;
|-&lt;br /&gt;
|'''2a.'''&lt;br /&gt;
|&amp;lt;tt&amp;gt;v = c.first()&amp;lt;/tt&amp;gt;&lt;br /&gt;
|erstes Element lesen (äquivalent zu &amp;lt;tt&amp;gt;v = c.get(0)&amp;lt;/tt&amp;gt;)&lt;br /&gt;
|-&lt;br /&gt;
|'''2b.'''&lt;br /&gt;
|&amp;lt;tt&amp;gt;v = c.last()&amp;lt;/tt&amp;gt;&lt;br /&gt;
|letztes Element lesen (äquivalent zu &amp;lt;tt&amp;gt;v = c.get(c.size()-1)&amp;lt;/tt&amp;gt;)&lt;br /&gt;
|-&lt;br /&gt;
|'''3a.'''&lt;br /&gt;
|&amp;lt;tt&amp;gt;v = c.smallest()&amp;lt;/tt&amp;gt;&lt;br /&gt;
|das kleinste Element lesen (dies bezieht sich auf eine Eigenschaft der Datenelemente bzw. Schlüssel, im Unterschied zu 2a, wo es um die Position im Container geht.)&lt;br /&gt;
|-&lt;br /&gt;
|'''3b.'''&lt;br /&gt;
|&amp;lt;tt&amp;gt;v = c.largest()&amp;lt;/tt&amp;gt;&lt;br /&gt;
|das größte Element lesen (dies bezieht sich auf eine Eigenschaft der Datenelemente bzw. Schlüssel, im Unterschied zu 2b, wo es um die Position im Container geht.)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
===Schreibender Zugriff===&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;0&amp;quot; cellpadding=&amp;quot;7&amp;quot; &lt;br /&gt;
|-valign=&amp;quot;top&amp;quot; &lt;br /&gt;
|'''4a.'''&lt;br /&gt;
|&amp;lt;tt&amp;gt;v.set(i, v)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|i-tes Element überschreiben (&amp;lt;tt&amp;gt;c.size()&amp;lt;/tt&amp;gt; bleibt unverändert)&lt;br /&gt;
|-&lt;br /&gt;
|'''4b.'''&lt;br /&gt;
|&amp;lt;tt&amp;gt;v.set(pos, v)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|Element an der Stelle &amp;lt;tt&amp;gt;pos&amp;lt;/tt&amp;gt; überschreiben (&amp;lt;tt&amp;gt;c.size()&amp;lt;/tt&amp;gt; bleibt unverändert. Zur Bedeutung von &amp;lt;tt&amp;gt;pos&amp;lt;/tt&amp;gt; siehe 1b.)&lt;br /&gt;
|-&lt;br /&gt;
|'''4c.'''&lt;br /&gt;
|&amp;lt;tt&amp;gt;v.set(key, v)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|Element mit dem Schlüssel &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; überschreiben (&amp;lt;tt&amp;gt;c.size()&amp;lt;/tt&amp;gt; bleibt unverändert)&lt;br /&gt;
|-&lt;br /&gt;
|'''5a.'''&lt;br /&gt;
|&amp;lt;tt&amp;gt;c.insert(i, v)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|Objekt als i-tes in den Container einfügen (Werte ab &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt; werden eine Position nach hinten verschoben, &amp;lt;tt&amp;gt;c.size()&amp;lt;/tt&amp;gt; erhöht sich um 1)&lt;br /&gt;
|-&lt;br /&gt;
|'''5b.'''&lt;br /&gt;
|&amp;lt;tt&amp;gt;c.insert(pos, v)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|Objekt an Position &amp;lt;tt&amp;gt;pos&amp;lt;/tt&amp;gt; in den Container einfügen (Werte ab &amp;lt;tt&amp;gt;pos&amp;lt;/tt&amp;gt; werden eine Position nach hinten verschoben, &amp;lt;tt&amp;gt;c.size()&amp;lt;/tt&amp;gt; erhöht sich um 1)&lt;br /&gt;
|-&lt;br /&gt;
|'''5c.'''&lt;br /&gt;
|&amp;lt;tt&amp;gt;c.insert(key, v)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|Objekt unter dem Schlüssel &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; in den Container einfügen (Wenn der Schlüssel schon vergeben war, wird ein Fehler signalisiert. &amp;lt;tt&amp;gt;c.size()&amp;lt;/tt&amp;gt; erhöht sich um 1). &lt;br /&gt;
|-&lt;br /&gt;
|'''5d.'''&lt;br /&gt;
|&amp;lt;tt&amp;gt;c.insert(v)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|Objekt an beliebiger Stelle in den Container einfügen (Der Container bestimmt die optimale Position selbst. &amp;lt;tt&amp;gt;c.size()&amp;lt;/tt&amp;gt; erhöht sich um 1). &lt;br /&gt;
|-&lt;br /&gt;
|'''6a.'''&lt;br /&gt;
|&amp;lt;tt&amp;gt;c.prepend(v)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|Objekt am Anfang einfügen (äquivalent zu &amp;lt;tt&amp;gt;c.insert(0, v)&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;c.size()&amp;lt;/tt&amp;gt; erhöht sich um 1)&lt;br /&gt;
|-&lt;br /&gt;
|'''6b.'''&lt;br /&gt;
|&amp;lt;tt&amp;gt;c.append(v)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|Objekt am Ende anhängen (äquivalent zu &amp;lt;tt&amp;gt;c.insert(c.size(), v)&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;c.size()&amp;lt;/tt&amp;gt; erhöht sich um 1)&lt;br /&gt;
|-&lt;br /&gt;
|'''7a.'''&lt;br /&gt;
|&amp;lt;tt&amp;gt;c.remove(i)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|i-tes Element aus dem Container löschen (Werte ab &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt; werden eine Position nach vorn verschoben, &amp;lt;tt&amp;gt;c.size()&amp;lt;/tt&amp;gt; verringert sich um 1)&lt;br /&gt;
|-&lt;br /&gt;
|'''7b.'''&lt;br /&gt;
|&amp;lt;tt&amp;gt;c.remove(pos)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|Objekt an Position &amp;lt;tt&amp;gt;pos&amp;lt;/tt&amp;gt; aus dem Container löschen  (Werte ab &amp;lt;tt&amp;gt;pos&amp;lt;/tt&amp;gt; werden eine Position nach vorn verschoben, &amp;lt;tt&amp;gt;c.size()&amp;lt;/tt&amp;gt; verringert sich um 1)&lt;br /&gt;
|-&lt;br /&gt;
|'''7c.'''&lt;br /&gt;
|&amp;lt;tt&amp;gt;c.remove(key)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|Objekt unter dem Schlüssel &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; aus dem Container löschen  (Wenn der Schlüssel nicht vergeben war, wird ein Fehler signalisiert. &amp;lt;tt&amp;gt;c.size()&amp;lt;/tt&amp;gt; verringert sich um 1)&lt;br /&gt;
|-&lt;br /&gt;
|'''8a.'''&lt;br /&gt;
|&amp;lt;tt&amp;gt;c.removeFirst()&amp;lt;/tt&amp;gt;&lt;br /&gt;
|das erste Element aus dem Container entfernen (äquivalent zu &amp;lt;tt&amp;gt;c.remove(0)&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;c.size()&amp;lt;/tt&amp;gt; verringert sich um 1)&lt;br /&gt;
|-&lt;br /&gt;
|'''8b.'''&lt;br /&gt;
|&amp;lt;tt&amp;gt;c.removeLast()&amp;lt;/tt&amp;gt;&lt;br /&gt;
|das letzte Element aus dem Container entfernen (äquivalent zu &amp;lt;tt&amp;gt;c.remove(c.size()-1)&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;c.size()&amp;lt;/tt&amp;gt; verringert sich um 1)&lt;br /&gt;
|-&lt;br /&gt;
|'''9a.'''&lt;br /&gt;
|&amp;lt;tt&amp;gt;c.removeSmallest()&amp;lt;/tt&amp;gt;&lt;br /&gt;
|das kleinste Element aus dem Container entfernen (dies bezieht sich auf eine Eigenschaft der Datenelemente bzw. Schlüssel, im Unterschied zu 8a, wo es um die Position im Container geht. &amp;lt;tt&amp;gt;c.size()&amp;lt;/tt&amp;gt; verringert sich um 1)&lt;br /&gt;
|-&lt;br /&gt;
|'''9b.'''&lt;br /&gt;
|&amp;lt;tt&amp;gt;c.removeLargest()&amp;lt;/tt&amp;gt;&lt;br /&gt;
|das größte Element aus dem Container entfernen (dies bezieht sich auf eine Eigenschaft der Datenelemente bzw. Schlüssel, im Unterschied zu 8b, wo es um die Position im Container geht. &amp;lt;tt&amp;gt;c.size()&amp;lt;/tt&amp;gt; verringert sich um 1)&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Facts==&lt;br /&gt;
&lt;br /&gt;
*Jede dieser Operationen kann sehr effizient implementiert werden.&lt;br /&gt;
*Keine Datenstruktur ist bekannt, die '''alle''' diese Operationen effizient implementiert.&lt;br /&gt;
&lt;br /&gt;
==Beispiele==&lt;br /&gt;
&lt;br /&gt;
Je nachdem welche Operation effizient sein soll, wird eine andere Container Datenstruktur ausgewählt. Die Operation &amp;lt;tt&amp;gt;c.size()&amp;lt;/tt&amp;gt; wird von allen Containern effizient unterstützt.&lt;br /&gt;
&lt;br /&gt;
===Arrays===&lt;br /&gt;
;'''(statisches) Array''' [http://en.wikipedia.org/wiki/Array]: Das Array ist die einfachste Datenstruktur, es kann einfach als aufeinanderfolgender Bereich von Speicherzellen implementiert werden. Jede dieser Speicherzellen nimmt ein Objekt als Datenelement auf. Die Größe ist nicht veränderbar (daher der Name ''statisch''). &amp;lt;br/&amp;gt; Das statische Array unterstützt die Operationen &lt;br /&gt;
    1a.    c.get(i)&lt;br /&gt;
    4a.    c.set(i, value)&lt;br /&gt;
;'''Dynamisches Array''' [http://en.wikipedia.org/wiki/Dynamic_array]: Die Größe ist veränderbar, aber nur durch Anfügen oder Entfernen eines Elements am ''Ende'' des Arrays. Die unterstützen Operationen sind dieselben wie die des statischen Arrays, zusätzlich unterstützt das dynamische Array die Operationen &lt;br /&gt;
    6b.    c.append(v)&lt;br /&gt;
    8b.    c.removeLast()&lt;br /&gt;
Wir beschreiben im Abschnitt [[Effizienz#dynamisches_Array|Amortisierte Komplexität]], wie man dies effizient implementieren kann. Das Anfügen neuer Elemente am Ende ist eine sehr häufige Operation, so dass das dynamische Array eine der beliebtesten Datenstrukturen ist. In Python hat das dynamische Array den Typ &amp;lt;tt&amp;gt;list&amp;lt;/tt&amp;gt;, was in diesem Fall nichts mit verketten Listen zu tun hat, sondern eher auf Listen im Sinne von Tabellen hinweist (die Namenswahl ist dennoch etwas unglücklich und kann zu Verwechslungen führen).&lt;br /&gt;
;'''assoziatives Array (Dictionary)''' [http://en.wikipedia.org/wiki/Associative_array]: Ein Dictionary verallgemeinert das dynamische Array: Während Arrays auf ihre Elemente über Indizes (= natürliche Zahlen) zugreifen, können die Schlüssel (Keys) bei einem Dictionary einen beliebigen Typ haben. Jedes Element des Dictionary besteht aus einem Schlüssel-Wert-Paar, jeder Schlüssel bekommt somit einen Wert zugewiesen. &amp;lt;br/&amp;gt;Das Dictionary unterstützt die Operationen &lt;br /&gt;
    1c.    c.get(key)&lt;br /&gt;
    4c.    c.set(key, value)&lt;br /&gt;
    5c.    c.insert(key, value)&lt;br /&gt;
    7c.    c.remove(key)&lt;br /&gt;
Wenn als Schlüssel natürliche Zahlen 0, 1, ..., N gewählt werden, sind dies im wesentlichen dieselben Operationen wie beim Array. Man wird das Dictionary also vor allem dann einsetzen, wenn die Schlüssel einen anderen Typ haben, oder wenn die Zahlen nicht aus dem zusammenhängenden Intervall 0, ..., N kommen. Das Python-Dictionary hat den Typ &amp;lt;tt&amp;gt;dict&amp;lt;/tt&amp;gt;. Wir behandeln diese Datenstruktur im Kapitel [[Hashing und assoziative Arrays]].&lt;br /&gt;
===verkettete Listen===&lt;br /&gt;
;'''(einfach) verkettete Liste''' [http://en.wikipedia.org/wiki/Linked_list#Singly-linked_list]: Im Gegensatz zum Array müssen die Speicherzellen nicht nacheinenander im Speicher abgelegt sein. Statt dessen enthält jedes Element der Liste ein Feld &amp;lt;tt&amp;gt;next&amp;lt;/tt&amp;gt;, das auf das nächste Element der Liste verweist. Um das i-te Element zu finden, muss man die Liste von vorn nach hinten durchlaufen. Deshalb ist die Operation &amp;lt;tt&amp;gt;c.get(i)&amp;lt;/tt&amp;gt; für verkettete Listen nicht effizient. Wenn man allerdings auf ein Element zugegriffen hat, kann man ein &amp;lt;tt&amp;gt;pos&amp;lt;/tt&amp;gt;-Objekt (in diesem Fall eine Referenz auf das Element) speichern, so dass ein erneuter Zugriff auf das selbe Element schnell geht. Das gleiche gilt für das folgende Element, weil man nur einmal &amp;lt;tt&amp;gt;pos = pos.next&amp;lt;/tt&amp;gt; aufrufen muss. Nur wenig komplizierter (und dadurch ebenfalls effizient) ist das Einfügen eines neuen Elements an der Position &amp;lt;tt&amp;gt;pos&amp;lt;/tt&amp;gt;. &amp;lt;br/&amp;gt;Die verkette Liste unterstützt somit die Operationen: &lt;br /&gt;
    1b.    c.get(pos)&lt;br /&gt;
    2a.    c.first()&lt;br /&gt;
    4b.    c.set(pos, value)&lt;br /&gt;
    5b.    c.insert(pos, value)&lt;br /&gt;
    6a.    c.prepend(value)&lt;br /&gt;
    7b.    c.remove(pos)&lt;br /&gt;
    8a.    c.removeFirst(pos)&lt;br /&gt;
Es scheint, dass die Liste eine sehr flexible Datenstruktur ist. Allerdings ist es ein gravierender Nachteil, dass &amp;lt;tt&amp;gt;pos&amp;lt;/tt&amp;gt; nur auf das jeweils nächste Element weitergesetzt werden kann. Im Gegensatz dazu können Indizes in einem Array effizient auf belibiebige Positionen gesetzt werden. Man bevorzugt deshalb heute dynamische Arrays.&lt;br /&gt;
;'''Doppelt verkettete Liste''' [http://en.wikipedia.org/wiki/Linked_list#Doubly-linked_list]: Im Gegensatz zur einfach verketteten Liste enthält jedes Element nicht nur einen Zeiger auf das darauffolgende, sondern auch auf das vorherige Element in der Liste. Dadurch kann ein &amp;lt;tt&amp;gt;pos&amp;lt;/tt&amp;gt;-Objekt auch effizient um ein Element zurückgesetzt werden: &amp;lt;tt&amp;gt;pos = pos.previous&amp;lt;/tt&amp;gt;.&amp;lt;br/&amp;gt; Die doppelt verkette Liste unterstützt deshalb die selben Operationen wie die einfach verkettete, und zusätzlich&lt;br /&gt;
    2b.    c.last()&lt;br /&gt;
    6b.    c.append(value)&lt;br /&gt;
    8b.    c.removeLast()&lt;br /&gt;
===Queues===&lt;br /&gt;
;'''Stack (Stapelspeicher)''' [http://en.wikipedia.org/wiki/Stack_(data_structure)]: Speichert/stapelt die Objekte mit push in einen Speicher. Wiederrum mit pop kann das oberste (=zuletzt eingefügte) Element herausgeholt werden: LIFO (Last In First Out) &amp;lt;br /&amp;gt;Die Python-Datenstruktur &amp;lt;tt&amp;gt;List&amp;lt;/tt&amp;gt; eignet sich beispielsweise als Stack.&amp;lt;br/&amp;gt;Operationen: &lt;br /&gt;
    2b.    c.last()         # auf das oberste Element zugreifen, ohne es zu entfernen&lt;br /&gt;
    6b.    c.append(value)  # Element auf den Stapel legen (beim Stack meist c.push(value) genannt) &lt;br /&gt;
    8b.    c.removeLast()   # oberstes Element entfernen (beim Stack meist c.pop() genannt)&lt;br /&gt;
;'''Queue (Schlange)''' [http://en.wikipedia.org/wiki/Queue_(data_structure)]: Eine Queue ist wie eine Warteschlange an der Kasse im Supermarkt, bedient wird derjenige der als erster an die Kasse kommt: FIFO (First In First Out)&amp;lt;br/&amp;gt;Operationen:  &lt;br /&gt;
    2a.    c.first()&lt;br /&gt;
    6b.    c.append(value)&lt;br /&gt;
    8a.    c.removeFirst()&lt;br /&gt;
;'''Deque (Double Ended Queue)''' [http://en.wikipedia.org/wiki/Deque]:  wie Stack + Queue, d.h. Objekte können am Ende eingefügt, aber sowohl vorn als auch hinten gelesen und entfernt werden.&amp;lt;br/&amp;gt;Operationen &lt;br /&gt;
    2a.    c.first()&lt;br /&gt;
    2b.    c.last()&lt;br /&gt;
    6b.    c.append(value)&lt;br /&gt;
    8a.    c.removeFirst()&lt;br /&gt;
    8b.    c.removeLast()&lt;br /&gt;
Die Deque ist Thema in [[Media:Übung-3.pdf|Übungsblatt 3]].&lt;br /&gt;
===Prioritätswarteschlangen===&lt;br /&gt;
;'''MinPriorityQueue''' [http://en.wikipedia.org/wiki/Priority_queue]: Warteschlange, die das Element mit der kleinsten Priorität zuerst zurückgibt (z.B. an der Kasse im Supermarkt diejenige, die die wenigsten Produkte kaufen möchte) &amp;lt;br/&amp;gt; Mögliche Operationen: &lt;br /&gt;
    3a.    c.smallest()&lt;br /&gt;
    5d.    c.insert(value)&lt;br /&gt;
    9a.    c.removeSmallest()&lt;br /&gt;
;'''MaxPriorityQueue''' [http://en.wikipedia.org/wiki/Priority_queue]: Warteschlange, die das Element mit der kleinsten Priorität zuerst zurückgibt &amp;lt;br/&amp;gt; Unterstützte Operationen sind: &lt;br /&gt;
    3b.    c.largest()&lt;br /&gt;
    5d.    c.insert(value)&lt;br /&gt;
    9b.    c.removeLargest()&lt;br /&gt;
;'''MinMaxPriorityQueue''' [http://en.wikipedia.org/wiki/Priority_queue]: kombiniert MinPriorityQueue + MaxPriorityQueue&lt;br /&gt;
&lt;br /&gt;
Die drei letzten Datenstrukturen behandeln wir im Kapitel [[Prioritätswarteschlangen]].&lt;/div&gt;</summary>
		<author><name>Jschleic</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Container&amp;diff=2527</id>
		<title>Container</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Container&amp;diff=2527"/>
				<updated>2008-07-22T09:14:56Z</updated>
		
		<summary type="html">&lt;p&gt;Jschleic: Gliederung&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;der Begriff '''Container''' stammt ursprünglich aus der Schiffahrt,&lt;br /&gt;
Container kann mehrere Bedeutungen annehmen.&lt;br /&gt;
 &lt;br /&gt;
'''in der Schifffahrt''': &amp;lt;br/&amp;gt;&lt;br /&gt;
Behälter der Güter lagern und transportieren kann.&lt;br /&gt;
&lt;br /&gt;
'''in der Datenverarbeitung''': &amp;lt;br/&amp;gt; &lt;br /&gt;
- '''Computertechnik''': eine Dateiformat die verschiedenartige Datenformate enthalten kann. (siehe [http://de.wikipedia.org/wiki/Containerformat Containerformat]) &amp;lt;br/&amp;gt;&lt;br /&gt;
- '''Server''': Teil einer Server-Software und Enterprise Java Beans (EJB) verwaltet. Der Container speichert die Daten und prüft die Verfügbarkeit für jeden autorisierten Client. (siehe [http://de.wikipedia.org/wiki/EJB-Container EJB-Container]) &amp;lt;br/&amp;gt;&lt;br /&gt;
- '''in Programmiersprachen''': Einige Programmiersprachen wie Java oder C++ verfügen über Containerklassen. In C++ sind sie in der Stardbibliothek [http://de.wikipedia.org/wiki/C%2B%2B-Standardbibliothek C++Standardbibliothek] &amp;lt;br/&amp;gt;&lt;br /&gt;
- '''in der Informatik''' (und bei uns in der Vorlesung): ein abstraktes Objekt, das Elemente des gleichen Typs speichert (Array, Liste, Dictionary,...) siehe auch [http://de.wikipedia.org/wiki/Datenstruktur Datenstrukturen]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Mögliche Operationen==&lt;br /&gt;
&lt;br /&gt;
Welche Operationen hätte man denn gerne bei einer solchen Container-Datenstruktur?&lt;br /&gt;
Was benötigen Algorithmen häufig? Wie sollten die Daten organisiert sein, damit Algorithmen effizient damit arbeiten können? &amp;lt;br/&amp;gt;&lt;br /&gt;
Eine solche Anforderungsanalyse ist sehr aufwendig und kann sich über Jahre erstrecken, weil Erfahrungen gesammelt werden müssen, welche Anforderungen an Datenstrukturen in vielen Algorithmen immer wieder auftreten. &amp;lt;br/&amp;gt;&lt;br /&gt;
Wir listen im folgenden nur das Resultat, also die wichtigsten Operationen von Container-Datenstrukturen auf.&lt;br /&gt;
  &lt;br /&gt;
Sei &amp;lt;tt&amp;gt;c&amp;lt;/tt&amp;gt; eine Container-Datenstruktur und &amp;lt;tt&amp;gt;v&amp;lt;/tt&amp;gt; ein darin gespeicherter Wert:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
===Lesender Zugriff===&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;0&amp;quot; cellpadding=&amp;quot;7&amp;quot; &lt;br /&gt;
|-valign=&amp;quot;top&amp;quot; &lt;br /&gt;
|'''0.'''&lt;br /&gt;
| &amp;lt;tt&amp;gt;c.size()&amp;lt;/tt&amp;gt;&lt;br /&gt;
|gibt die Anzahl der Elemente im Container an&lt;br /&gt;
|-&lt;br /&gt;
|'''1a.'''&lt;br /&gt;
|&amp;lt;tt&amp;gt;v = c.get(i)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|das i-te Element im Container lesen&lt;br /&gt;
|-&lt;br /&gt;
|'''1b.'''&lt;br /&gt;
|&amp;lt;tt&amp;gt;v = c.get(pos)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|das Element an Position &amp;lt;tt&amp;gt;pos&amp;lt;/tt&amp;gt; lesen (&amp;lt;tt&amp;gt;pos&amp;lt;/tt&amp;gt; ist ein geeignetes Hilfsobjekt, das in Abhängigkeit von der Art der Datenstruktur eine Position im Container referenziert. Im Falle 1a. &amp;lt;tt&amp;gt;v = c.get(i)&amp;lt;/tt&amp;gt; ist &amp;lt;tt&amp;gt;pos&amp;lt;/tt&amp;gt; eine natürliche Zahl, aber es gibt auch andere Möglichkeiten, die Position zu kodieren.)&lt;br /&gt;
|-&lt;br /&gt;
|'''1c.'''&lt;br /&gt;
|&amp;lt;tt&amp;gt;v = c.get(key)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|das Element mit dem Schlüssel &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; lesen (Beachte den Unterschied zu 1b: In 1b markiert &amp;lt;tt&amp;gt;pos&amp;lt;/tt&amp;gt; eine Position im Container, hier in 1c bezieht sich &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; auf eine Eigenschaft der Datenelemente, die von der Position im Container unabhängig ist.)&lt;br /&gt;
|-&lt;br /&gt;
|'''2a.'''&lt;br /&gt;
|&amp;lt;tt&amp;gt;v = c.first()&amp;lt;/tt&amp;gt;&lt;br /&gt;
|erstes Element lesen (äquivalent zu &amp;lt;tt&amp;gt;v = c.get(0)&amp;lt;/tt&amp;gt;)&lt;br /&gt;
|-&lt;br /&gt;
|'''2b.'''&lt;br /&gt;
|&amp;lt;tt&amp;gt;v = c.last()&amp;lt;/tt&amp;gt;&lt;br /&gt;
|letztes Element lesen (äquivalent zu &amp;lt;tt&amp;gt;v = c.get(c.size()-1)&amp;lt;/tt&amp;gt;)&lt;br /&gt;
|-&lt;br /&gt;
|'''3a.'''&lt;br /&gt;
|&amp;lt;tt&amp;gt;v = c.smallest()&amp;lt;/tt&amp;gt;&lt;br /&gt;
|das kleinste Element lesen (dies bezieht sich auf eine Eigenschaft der Datenelemente bzw. Schlüssel, im Unterschied zu 2a, wo es um die Position im Container geht.)&lt;br /&gt;
|-&lt;br /&gt;
|'''3b.'''&lt;br /&gt;
|&amp;lt;tt&amp;gt;v = c.largest()&amp;lt;/tt&amp;gt;&lt;br /&gt;
|das größte Element lesen (dies bezieht sich auf eine Eigenschaft der Datenelemente bzw. Schlüssel, im Unterschied zu 2b, wo es um die Position im Container geht.)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
===Schreibender Zugriff===&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;0&amp;quot; cellpadding=&amp;quot;7&amp;quot; &lt;br /&gt;
|-valign=&amp;quot;top&amp;quot; &lt;br /&gt;
|'''4a.'''&lt;br /&gt;
|&amp;lt;tt&amp;gt;v.set(i, v)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|i-tes Element überschreiben (&amp;lt;tt&amp;gt;c.size()&amp;lt;/tt&amp;gt; bleibt unverändert)&lt;br /&gt;
|-&lt;br /&gt;
|'''4b.'''&lt;br /&gt;
|&amp;lt;tt&amp;gt;v.set(pos, v)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|Element an der Stelle &amp;lt;tt&amp;gt;pos&amp;lt;/tt&amp;gt; überschreiben (&amp;lt;tt&amp;gt;c.size()&amp;lt;/tt&amp;gt; bleibt unverändert. Zur Bedeutung von &amp;lt;tt&amp;gt;pos&amp;lt;/tt&amp;gt; siehe 1b.)&lt;br /&gt;
|-&lt;br /&gt;
|'''4c.'''&lt;br /&gt;
|&amp;lt;tt&amp;gt;v.set(key, v)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|Element mit dem Schlüssel &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; überschreiben (&amp;lt;tt&amp;gt;c.size()&amp;lt;/tt&amp;gt; bleibt unverändert)&lt;br /&gt;
|-&lt;br /&gt;
|'''5a.'''&lt;br /&gt;
|&amp;lt;tt&amp;gt;c.insert(i, v)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|Objekt als i-tes in den Container einfügen (Werte ab &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt; werden eine Position nach hinten verschoben, &amp;lt;tt&amp;gt;c.size()&amp;lt;/tt&amp;gt; erhöht sich um 1)&lt;br /&gt;
|-&lt;br /&gt;
|'''5b.'''&lt;br /&gt;
|&amp;lt;tt&amp;gt;c.insert(pos, v)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|Objekt an Position &amp;lt;tt&amp;gt;pos&amp;lt;/tt&amp;gt; in den Container einfügen (Werte ab &amp;lt;tt&amp;gt;pos&amp;lt;/tt&amp;gt; werden eine Position nach hinten verschoben, &amp;lt;tt&amp;gt;c.size()&amp;lt;/tt&amp;gt; erhöht sich um 1)&lt;br /&gt;
|-&lt;br /&gt;
|'''5c.'''&lt;br /&gt;
|&amp;lt;tt&amp;gt;c.insert(key, v)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|Objekt unter dem Schlüssel &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; in den Container einfügen (Wenn der Schlüssel schon vergeben war, wird ein Fehler signalisiert. &amp;lt;tt&amp;gt;c.size()&amp;lt;/tt&amp;gt; erhöht sich um 1). &lt;br /&gt;
|-&lt;br /&gt;
|'''5d.'''&lt;br /&gt;
|&amp;lt;tt&amp;gt;c.insert(v)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|Objekt an beliebiger Stelle in den Container einfügen (Der Container bestimmt die optimale Position selbst. &amp;lt;tt&amp;gt;c.size()&amp;lt;/tt&amp;gt; erhöht sich um 1). &lt;br /&gt;
|-&lt;br /&gt;
|'''6a.'''&lt;br /&gt;
|&amp;lt;tt&amp;gt;c.prepend(v)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|Objekt am Anfang einfügen (äquivalent zu &amp;lt;tt&amp;gt;c.insert(0, v)&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;c.size()&amp;lt;/tt&amp;gt; erhöht sich um 1)&lt;br /&gt;
|-&lt;br /&gt;
|'''6b.'''&lt;br /&gt;
|&amp;lt;tt&amp;gt;c.append(v)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|Objekt am Ende anhängen (äquivalent zu &amp;lt;tt&amp;gt;c.insert(c.size(), v)&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;c.size()&amp;lt;/tt&amp;gt; erhöht sich um 1)&lt;br /&gt;
|-&lt;br /&gt;
|'''7a.'''&lt;br /&gt;
|&amp;lt;tt&amp;gt;c.remove(i)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|i-tes Element aus dem Container löschen (Werte ab &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt; werden eine Position nach vorn verschoben, &amp;lt;tt&amp;gt;c.size()&amp;lt;/tt&amp;gt; verringert sich um 1)&lt;br /&gt;
|-&lt;br /&gt;
|'''7b.'''&lt;br /&gt;
|&amp;lt;tt&amp;gt;c.remove(pos)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|Objekt an Position &amp;lt;tt&amp;gt;pos&amp;lt;/tt&amp;gt; aus dem Container löschen  (Werte ab &amp;lt;tt&amp;gt;pos&amp;lt;/tt&amp;gt; werden eine Position nach vorn verschoben, &amp;lt;tt&amp;gt;c.size()&amp;lt;/tt&amp;gt; verringert sich um 1)&lt;br /&gt;
|-&lt;br /&gt;
|'''7c.'''&lt;br /&gt;
|&amp;lt;tt&amp;gt;c.remove(key)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|Objekt unter dem Schlüssel &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; aus dem Container löschen  (Wenn der Schlüssel nicht vergeben war, wird ein Fehler signalisiert. &amp;lt;tt&amp;gt;c.size()&amp;lt;/tt&amp;gt; verringert sich um 1)&lt;br /&gt;
|-&lt;br /&gt;
|'''8a.'''&lt;br /&gt;
|&amp;lt;tt&amp;gt;c.removeFirst()&amp;lt;/tt&amp;gt;&lt;br /&gt;
|das erste Element aus dem Container entfernen (äquivalent zu &amp;lt;tt&amp;gt;c.remove(0)&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;c.size()&amp;lt;/tt&amp;gt; verringert sich um 1)&lt;br /&gt;
|-&lt;br /&gt;
|'''8b.'''&lt;br /&gt;
|&amp;lt;tt&amp;gt;c.removeLast()&amp;lt;/tt&amp;gt;&lt;br /&gt;
|das letzte Element aus dem Container entfernen (äquivalent zu &amp;lt;tt&amp;gt;c.remove(c.size()-1)&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;c.size()&amp;lt;/tt&amp;gt; verringert sich um 1)&lt;br /&gt;
|-&lt;br /&gt;
|'''9a.'''&lt;br /&gt;
|&amp;lt;tt&amp;gt;c.removeSmallest()&amp;lt;/tt&amp;gt;&lt;br /&gt;
|das kleinste Element aus dem Container entfernen (dies bezieht sich auf eine Eigenschaft der Datenelemente bzw. Schlüssel, im Unterschied zu 8a, wo es um die Position im Container geht. &amp;lt;tt&amp;gt;c.size()&amp;lt;/tt&amp;gt; verringert sich um 1)&lt;br /&gt;
|-&lt;br /&gt;
|'''9b.'''&lt;br /&gt;
|&amp;lt;tt&amp;gt;c.removeLargest()&amp;lt;/tt&amp;gt;&lt;br /&gt;
|das größte Element aus dem Container entfernen (dies bezieht sich auf eine Eigenschaft der Datenelemente bzw. Schlüssel, im Unterschied zu 8b, wo es um die Position im Container geht. &amp;lt;tt&amp;gt;c.size()&amp;lt;/tt&amp;gt; verringert sich um 1)&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Facts==&lt;br /&gt;
&lt;br /&gt;
*Jede dieser Operationen kann sehr effizient implementiert werden.&lt;br /&gt;
*Keine Datenstruktur ist bekannt, die '''alle''' diese Operationen effizient implementiert.&lt;br /&gt;
&lt;br /&gt;
==Beispiele==&lt;br /&gt;
&lt;br /&gt;
Je nachdem welche Operation effizient sein soll, wird eine andere Container Datenstruktur ausgewählt. Die Operation &amp;lt;tt&amp;gt;c.size()&amp;lt;/tt&amp;gt; wird von allen Containern effizient unterstützt.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
;'''(statisches) Array''' [http://en.wikipedia.org/wiki/Array]: Das Array ist die einfachste Datenstruktur, es kann einfach als aufeinanderfolgender Bereich von Speicherzellen implementiert werden. Jede dieser Speicherzellen nimmt ein Objekt als Datenelement auf. Die Größe ist nicht veränderbar (daher der Name ''statisch''). &amp;lt;br/&amp;gt; Das statische Array unterstützt die Operationen &lt;br /&gt;
    1a.    c.get(i)&lt;br /&gt;
    4a.    c.set(i, value)&lt;br /&gt;
;'''Dynamisches Array''' [http://en.wikipedia.org/wiki/Dynamic_array]: Die Größe ist veränderbar, aber nur durch Anfügen oder Entfernen eines Elements am ''Ende'' des Arrays. Die unterstützen Operationen sind dieselben wie die des statischen Arrays, zusätzlich unterstützt das dynamische Array die Operationen &lt;br /&gt;
    6b.    c.append(v)&lt;br /&gt;
    8b.    c.removeLast()&lt;br /&gt;
Wir beschreiben im Abschnitt [[Effizienz#dynamisches_Array|Amortisierte Komplexität]], wie man dies effizient implementieren kann. Das Anfügen neuer Elemente am Ende ist eine sehr häufige Operation, so dass das dynamische Array eine der beliebtesten Datenstrukturen ist. In Python hat das dynamische Array den Typ &amp;lt;tt&amp;gt;list&amp;lt;/tt&amp;gt;, was in diesem Fall nichts mit verketten Listen zu tun hat, sondern eher auf Listen im Sinne von Tabellen hinweist (die Namenswahl ist dennoch etwas unglücklich und kann zu Verwechslungen führen).&lt;br /&gt;
;'''assoziatives Array (Dictionary)''' [http://en.wikipedia.org/wiki/Associative_array]: Ein Dictionary verallgemeinert das dynamische Array: Während Arrays auf ihre Elemente über Indizes (= natürliche Zahlen) zugreifen, können die Schlüssel (Keys) bei einem Dictionary einen beliebigen Typ haben. Jedes Element des Dictionary besteht aus einem Schlüssel-Wert-Paar, jeder Schlüssel bekommt somit einen Wert zugewiesen. &amp;lt;br/&amp;gt;Das Dictionary unterstützt die Operationen &lt;br /&gt;
    1c.    c.get(key)&lt;br /&gt;
    4c.    c.set(key, value)&lt;br /&gt;
    5c.    c.insert(key, value)&lt;br /&gt;
    7c.    c.remove(key)&lt;br /&gt;
Wenn als Schlüssel natürliche Zahlen 0, 1, ..., N gewählt werden, sind dies im wesentlichen dieselben Operationen wie beim Array. Man wird das Dictionary also vor allem dann einsetzen, wenn die Schlüssel einen anderen Typ haben, oder wenn die Zahlen nicht aus dem zusammenhängenden Intervall 0, ..., N kommen. Das Python-Dictionary hat den Typ &amp;lt;tt&amp;gt;dict&amp;lt;/tt&amp;gt;. Wir behandeln diese Datenstruktur im Kapitel [[Hashing und assoziative Arrays]].&lt;br /&gt;
;'''(einfach) verkettete Liste''' [http://en.wikipedia.org/wiki/Linked_list#Singly-linked_list]: Im Gegensatz zum Array müssen die Speicherzellen nicht nacheinenander im Speicher abgelegt sein. Statt dessen enthält jedes Element der Liste ein Feld &amp;lt;tt&amp;gt;next&amp;lt;/tt&amp;gt;, das auf das nächste Element der Liste verweist. Um das i-te Element zu finden, muss man die Liste von vorn nach hinten durchlaufen. Deshalb ist die Operation &amp;lt;tt&amp;gt;c.get(i)&amp;lt;/tt&amp;gt; für verkettete Listen nicht effizient. Wenn man allerdings auf ein Element zugegriffen hat, kann man ein &amp;lt;tt&amp;gt;pos&amp;lt;/tt&amp;gt;-Objekt (in diesem Fall eine Referenz auf das Element) speichern, so dass ein erneuter Zugriff auf das selbe Element schnell geht. Das gleiche gilt für das folgende Element, weil man nur einmal &amp;lt;tt&amp;gt;pos = pos.next&amp;lt;/tt&amp;gt; aufrufen muss. Nur wenig komplizierter (und dadurch ebenfalls effizient) ist das Einfügen eines neuen Elements an der Position &amp;lt;tt&amp;gt;pos&amp;lt;/tt&amp;gt;. &amp;lt;br/&amp;gt;Die verkette Liste unterstützt somit die Operationen: &lt;br /&gt;
    1b.    c.get(pos)&lt;br /&gt;
    2a.    c.first()&lt;br /&gt;
    4b.    c.set(pos, value)&lt;br /&gt;
    5b.    c.insert(pos, value)&lt;br /&gt;
    6a.    c.prepend(value)&lt;br /&gt;
    7b.    c.remove(pos)&lt;br /&gt;
    8a.    c.removeFirst(pos)&lt;br /&gt;
Es scheint, dass die Liste eine sehr flexible Datenstruktur ist. Allerdings ist es ein gravierender Nachteil, dass &amp;lt;tt&amp;gt;pos&amp;lt;/tt&amp;gt; nur auf das jeweils nächste Element weitergesetzt werden kann. Im Gegensatz dazu können Indizes in einem Array effizient auf belibiebige Positionen gesetzt werden. Man bevorzugt deshalb heute dynamische Arrays.&lt;br /&gt;
;'''Doppelt verkettete Liste''' [http://en.wikipedia.org/wiki/Linked_list#Doubly-linked_list]: Im Gegensatz zur einfach verketteten Liste enthält jedes Element nicht nur einen Zeiger auf das darauffolgende, sondern auch auf das vorherige Element in der Liste. Dadurch kann ein &amp;lt;tt&amp;gt;pos&amp;lt;/tt&amp;gt;-Objekt auch effizient um ein Element zurückgesetzt werden: &amp;lt;tt&amp;gt;pos = pos.previous&amp;lt;/tt&amp;gt;.&amp;lt;br/&amp;gt; Die doppelt verkette Liste unterstützt deshalb die selben Operationen wie die einfach verkettete, und zusätzlich&lt;br /&gt;
    2b.    c.last()&lt;br /&gt;
    6b.    c.append(value)&lt;br /&gt;
    8b.    c.removeLast()&lt;br /&gt;
;'''Stack (Stapelspeicher)''' [http://en.wikipedia.org/wiki/Stack_(data_structure)]: Speichert/stapelt die Objekte mit push in einen Speicher. Wiederrum mit pop kann das oberste (=zuletzt eingefügte) Element herausgeholt werden: LIFO (Last In First Out) &amp;lt;br/&amp;gt;Operationen: &lt;br /&gt;
    2b.    c.last()         # auf das oberste Element zugreifen, ohne es zu entfernen&lt;br /&gt;
    6b.    c.append(value)  # Element auf den Stapel legen (beim Stack meist c.push(value) genannt) &lt;br /&gt;
    8b.    c.removeLast()   # oberstes Element entfernen (beim Stack meist c.pop() genannt)&lt;br /&gt;
;'''Queue (Schlange)''' [http://en.wikipedia.org/wiki/Queue_(data_structure)]: Eine Queue ist wie eine Warteschlange an der Kasse im Supermarkt, bedient wird derjenige der als erster an die Kasse kommt: FIFO (First In First Out)&amp;lt;br/&amp;gt;Operationen:  &lt;br /&gt;
    2a.    c.first()&lt;br /&gt;
    6b.    c.append(value)&lt;br /&gt;
    8a.    c.removeFirst()&lt;br /&gt;
;'''Deque (Double Ended Queue)''' [http://en.wikipedia.org/wiki/Deque]:  wie Stack + Queue, d.h. Objekte können am Ende eingefügt, aber sowohl vorn als auch hinten gelesen und entfernt werden.&amp;lt;br/&amp;gt;Operationen &lt;br /&gt;
    2a.    c.first()&lt;br /&gt;
    2b.    c.last()&lt;br /&gt;
    6b.    c.append(value)&lt;br /&gt;
    8a.    c.removeFirst()&lt;br /&gt;
    8b.    c.removeLast()&lt;br /&gt;
Die Deque ist Thema in [[Media:Übung-3.pdf|Übungsblatt 3]].&lt;br /&gt;
;'''MinPriorityQueue''' [http://en.wikipedia.org/wiki/Priority_queue]: Warteschlange, die das Element mit der kleinsten Priorität zuerst zurückgibt (z.B. an der Kasse im Supermarkt diejenige, die die wenigsten Produkte kaufen möchte) &amp;lt;br/&amp;gt; Mögliche Operationen: &lt;br /&gt;
    3a.    c.smallest()&lt;br /&gt;
    5d.    c.insert(value)&lt;br /&gt;
    9a.    c.removeSmallest()&lt;br /&gt;
;'''MaxPriorityQueue''' [http://en.wikipedia.org/wiki/Priority_queue]: Warteschlange, die das Element mit der kleinsten Priorität zuerst zurückgibt &amp;lt;br/&amp;gt; Unterstützte Operationen sind: &lt;br /&gt;
    3b.    c.largest()&lt;br /&gt;
    5d.    c.insert(value)&lt;br /&gt;
    9b.    c.removeLargest()&lt;br /&gt;
;'''MinMaxPriorityQueue''' [http://en.wikipedia.org/wiki/Priority_queue]: kombiniert MinPriorityQueue + MaxPriorityQueue&lt;br /&gt;
&lt;br /&gt;
Die drei letzten Datenstrukturen behandeln wir im Kapitel [[Prioritätswarteschlangen]].&lt;/div&gt;</summary>
		<author><name>Jschleic</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Effizienz&amp;diff=2526</id>
		<title>Effizienz</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Effizienz&amp;diff=2526"/>
				<updated>2008-07-22T09:03:31Z</updated>
		
		<summary type="html">&lt;p&gt;Jschleic: typo&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Bei der Diskussion von Effizienz müssen wir zwischen der Laufzeit eines Algorithmus auf einem bestimmten System und seiner prinzipiellen Leistungsfähigkeit (Algorithmenkomplexität) unterscheiden. Der Benutzer ist natürlich vor allem an der Laufzeit interessiert, denn diese bestimmt letztendlich seine Arbeitsproduktivität. Ein Softwaredesigner hingegen muss eine Implementation wählen, die auf verschiedenen Systemen und in verschiedenen Anwendungen schnell ist. Für ihn sind daher auch Aussagen zur Algorithmenkomplexität sehr wichtig, um den am besten geeigneten Algorithmus auszuwählen.&lt;br /&gt;
&lt;br /&gt;
== Laufzeit ==&lt;br /&gt;
&lt;br /&gt;
Aus Anwendersicht ist ein Algorithmus effizient, wenn er die in der Spezifikation verlangten Laufzeitgrenzen einhält. Ein Algorithmus muss also nicht immer so schnell wie möglich sein, sondern so schnell wie nötig. Dies führt in verschiedenen Anwendungen zu ganz unterschiedliche Laufzeitanforderungen:&lt;br /&gt;
&lt;br /&gt;
* Berechnen des nächsten Steuerkommandos für eine Maschine: ca. 1/1000s&lt;br /&gt;
* Berechnen des nächsten Bildes für eine Videopräsentation (z.B. Dekompression von MPEG-kodierten Bildern): ca. 1/25s&lt;br /&gt;
: Geringere Bildraten führen zu ruckeligen Filmen.&lt;br /&gt;
* Sichtbare Antwort auf ein interaktives Kommando (z.B. Mausklick): ca. 1/2s&lt;br /&gt;
: Wird diese Antwortzeit überschritten, vermuten viele Benutzer, dass der Mausklick nicht funktioniert hat, und klicken nochmals, mit eventuell fatalen Folgen. Wenn ein Algorithmus notwendigerweise länger dauert als 1/2s, sollte ein Fortschrittsbalken angezeigt werden.&lt;br /&gt;
* Wettervorhersage: muss spätestens am Vorabend des vorhergesagten Tages beendet sein&lt;br /&gt;
&lt;br /&gt;
===Laufzeitvergleich===&lt;br /&gt;
&lt;br /&gt;
Da die Laufzeit für den Benutzer ein so wichtiges Kriterium ist, werden häufig Laufzeitvergleiche durchgeführt. Deren Ergebnisse hängen allerdings von vielen Faktoren ab, die möglicherweise nicht kontrollierbar sind:&lt;br /&gt;
* Geschwindigkeit und Anzahl der Prozessoren&lt;br /&gt;
* Auslastung des Systems&lt;br /&gt;
* Größe des Hauptspeichers  und Cache, Geschwindigkeit des  Datenbus&lt;br /&gt;
* Qualität des Compilers/Optimierers (ist der Compiler für die spezielle Prozessor-Architektur optimiert?)&lt;br /&gt;
* Geschick des Programmierers&lt;br /&gt;
* Daten (Beispiel Quicksort: Best case und worst case [vorsortierter Input] stark unterschiedlich)&lt;br /&gt;
All diese Faktoren sind untereinander abhängig. Laufzeitvergleiche sind daher mit Vorsicht zu interpretieren.&lt;br /&gt;
Generell sollten bei Vergleichen möglichst wenige Parameter verändert werden, z.B.&lt;br /&gt;
* gleiches Programm (gleiche Kompilierung), gleiche Daten, andere Prozessoren&lt;br /&gt;
oder&lt;br /&gt;
* gleiche CPU, Daten, andere Programme (Vergleich von Algorithmen)&lt;br /&gt;
Zur Verbesserung der Vergleichbarkeit gibt es standardisierte [http://en.wikipedia.org/wiki/Benchmark_(computing) Benchmarks], die bestimmte Aspekte eines Systems unter möglichst realitätsnahen Bedingungen testen. Generell gilt aber: Durch Laufzeitmessung ist schwer festzustellen, ob ein Algorithmus ''prinzipiell'' besser ist als ein anderer. Dafür ist die Analyse der [[Effizienz#Algorithmen-Komplexität|Algorithmenkomplexität]] notwendig.&lt;br /&gt;
&lt;br /&gt;
===Optimierung der Laufzeit===&lt;br /&gt;
&lt;br /&gt;
Wenn sich herausstellt, dass ein bereits implementierter Algorithmus zu langsam läuft, geht man wie folgt vor:&lt;br /&gt;
&lt;br /&gt;
# Man verwendet einen [http://en.wikipedia.org/wiki/Performance_analysis Profiler], um zunächst den Flaschenhals zu bestimmen. Ein Profiler ist ein Hilfsprogramm, das während der Ausführung eines Programms misst, wieviel Zeit in jeder Funktion und Unterfunktion verbraucht wird. Dadurch kann man herausfinden, welcher Teil des Algorithmus überhaupt Probleme bereitet. Donald Knuth gibt z.B. als Erfahrungswert an, dass Programme während des größten Teils ihrer Laufzeit nur 3% des Quellcodes (natürlich mehrmals wiederholt) ausführen [http://pplab.snu.ac.kr/courses/adv_pl05/papers/p261-knuth.pdf]. Es ist sehr wichtig, diese 3% experimentell zu bestimmen, weil die Erfahrung zeigt, dass man beim Erraten der kritischen Programmteile oft falsch liegt. Man spricht dann von &amp;quot;[http://en.wikipedia.org/wiki/Optimization_%28computer_science%29#When_to_optimize premature optimization]&amp;quot;, also von voreiliger Optimierung ohne experimentelle Untersuchung der wirklichen Laufzeiten, was laut Knuth &amp;quot;the root of all evil&amp;quot; ist. Der Python-Profiler wird in [http://docs.python.org/lib/profile.html Kapitel 25] der Python-Dokumentation beschrieben.&lt;br /&gt;
# Man kann dann versuchen, die kritischen Programmteile zu optimieren.&lt;br /&gt;
# Falls der Laufzeitgewinn durch Optimierung zu gering ist, muss man einen prinzipiell schnelleren Algorithmus verwenden, falls es einen gibt.&lt;br /&gt;
&lt;br /&gt;
Einige wichtige Techniken der Programmoptimierung sollen hier erwähnt werden. Wenn man einen optimierenden Compiler verwendet, werden einige Optimierungen automatisch ausgeführt [http://en.wikipedia.org/wiki/Compiler_optimization]. In Python trifft dies jedoch nicht zu. Um den Sinn einiger Optimierungen zu verstehen, benötigt man Grundkenntnisse der Computerarchitektur.&lt;br /&gt;
&lt;br /&gt;
;Elimination von redundantem Code: Es ist offensichtlich überflüssig, dasselbe Ergebnis mehrmals zu berechnen, wenn es auch zwischengespeichert werden könnte. Diese Optimierung wird von vielen automatischen Optimierern unterstützt und kommt im wesentlichen in zwei Ausprägungen vor:&lt;br /&gt;
:; common subexpression elimination: In mathematischen Ausdrücken wird ein Teilergebnis häufig mehrmals benötigt. Man betrachte z.B. die Lösung der quadratischen Gleichung &amp;lt;math&amp;gt;x^2+p\,x+q&amp;lt;/math&amp;gt;:&lt;br /&gt;
        x1 = - p / 2.0 + sqrt(p*p/4.0 - q)&lt;br /&gt;
        x2 = - p / 2.0 - sqrt(p*p/4.0 - q)&lt;br /&gt;
::Die mehrmalige Berechnung von Teilausdrücken wird vermieden, wenn man stattdessen schreibt:&lt;br /&gt;
        p2 = - p / 2.0&lt;br /&gt;
        r  = sqrt(p2*p2 - q)&lt;br /&gt;
        x1 = p2 + r&lt;br /&gt;
        x2 = p2 - r&lt;br /&gt;
:; loop invariant elimination: Wenn ein Teilausdruck sich in einer Schleife nicht ändert, muss man ihn nicht bei jedem Schleifendurchlauf neu berechnen, sondern kann dies einmal vor Beginn der Schleife tun. Ein typisches Beispiel hierfür ist die Adressierung von Matrizen, die als 1-dimensionales Array gespeichert sind. Angenommen, wir speichern eine NxN Matrix &amp;lt;tt&amp;gt;m&amp;lt;/tt&amp;gt; in einem Array &amp;lt;tt&amp;gt;a&amp;lt;/tt&amp;gt; der Größe N&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, so dass das Matrixelement &amp;lt;tt&amp;gt;m&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt;&amp;lt;/tt&amp;gt; durch &amp;lt;tt&amp;gt;a[i + j*N]&amp;lt;/tt&amp;gt; indexiert wird. Wir betrachten die Aufgabe, eine Einheitsmatrix zu initialisieren. Ein nicht optimierter Algorithmus dafür lautet:&lt;br /&gt;
       for j in range(N):&lt;br /&gt;
           for i in range(N):&lt;br /&gt;
               if i == j:&lt;br /&gt;
                    a[i + j*N] = 1.0&lt;br /&gt;
               else:&lt;br /&gt;
                    a[i + j*N] = 0.0&lt;br /&gt;
::Der Ausdruck &amp;lt;tt&amp;gt;j*N&amp;lt;/tt&amp;gt; wird hier in jedem Schleifendurchlauf erneut berechnet, obwohl sich &amp;lt;tt&amp;gt;j&amp;lt;/tt&amp;gt; in der inneren Schleife gar nicht verändert. Man kann deshalb optimieren zu:&lt;br /&gt;
       for j in range(N):&lt;br /&gt;
           jN = j*N&lt;br /&gt;
           for i in range(N):&lt;br /&gt;
               if i == j:&lt;br /&gt;
                    a[i + jN] = 1.0&lt;br /&gt;
               else:&lt;br /&gt;
                    a[i + jN] = 0.0&lt;br /&gt;
;Vereinfachung der inneren Schleife: Generell sollte man sich bei der Optimierung auf die innere Schleife eines Algorithmus konzentrieren, weil dieser Code am häufigsten ausgeführt wird. Insbesondere sollte man die Anzahl der Befehle in der inneren Schleife so gering wie möglich halten und teure Befehle vermeiden. Früher waren vor allem Floating-Point Befehle teuer, die man oft durch die schnellere Integer-Arithmetik ersetzt hat, falls dies algorithmisch möglich war (diesen Rat findet man noch oft in der Literatur). Heute hat sich die Hardware so verbessert, dass im Allgemeinen nur noch die Floating-Point Division deutlich langsamer ist als die anderen Operatoren. Im obigen Beispiel der quadratischen Gleichung ist es daher sinnvoll, den Ausdruck &lt;br /&gt;
        p2 = -p / 2.0&lt;br /&gt;
:durch&lt;br /&gt;
        p2 = -0.5 * p&lt;br /&gt;
:zu ersetzen. Dadurch ersetzt man eine Division durch eine Multiplikation und spart außerdem das Negieren von &amp;lt;tt&amp;gt;p&amp;lt;/tt&amp;gt;, da der Compiler direkt mit &amp;lt;tt&amp;gt;-0.5&amp;lt;/tt&amp;gt; multipliziert.&lt;br /&gt;
;Ausnutzung der Prozessor-Pipeline: Moderne Prozessoren führen mehrere Befehle parallel aus. Dies ist möglich, weil jeder Befehl in mehrere Teilschritte zerlegt werden kann. Eine generische Unterteilung in vier Teilschritte ist z.B.:&lt;br /&gt;
:# Dekodieren des nächsten Befehls&lt;br /&gt;
:# Beschaffen der Daten, die der Befehl verwendet (aus Prozessorregistern, dem Cache, oder dem Hauptspeicher)&lt;br /&gt;
:# Ausführen des Befehls&lt;br /&gt;
:# Schreiben der Ergebnisse&lt;br /&gt;
:Man bezeichnet dies als die &amp;quot;[http://en.wikipedia.org/wiki/Instruction_pipeline instruction pipeline]&amp;quot; des Prozessors (heutige Prozessoren verwenden wesentlich feinere Unterteilungen). Prozessoren werden nun so gebaut, dass mehrere Befehle parallel, auf verschiedenen Ausführungsstufen ausgeführt werden. Wenn Befehl 1 also beim Schreiben der Ergebnisse angelangt ist, kann Befehl 2 die Hardware zum Ausführen des Befehls benutzen, während Befehl 3 seine Daten holt, und Befehl 4 soeben dekodiert wird. Unter bestimmten Bedingungen funktioniert diese Parallelverarbeitung jedoch nicht. Dies gibt Anlass zu Optimierungen:&lt;br /&gt;
:;Vermeiden unnötiger Typkonvertierungen: Der Prozessor verarbeitet Interger- und Floating-Point-Befehle in verschiedenen Pipelines, weil die Hardwareanforderungen sehr verschieden sind. Wird jetzt ein Ergebnis von Integer nach Floating-Point umgewandelt oder umgekehrt, muss die jeweils andere Pipeline warten, bis die erste Pipeline ihre Berechnung beendet. Es kann dann besser sein, Berechnungen in Floating-Point zu Ende zu führen, auch wenn sie semantisch eigentlich Integer-Berechnungen sind.&lt;br /&gt;
:;Reduzierung der Anzahl von Verzweigungen: Wenn der Code verzweigt (z.B. durch eine &amp;lt;tt&amp;gt;if&amp;lt;/tt&amp;gt;- oder  &amp;lt;tt&amp;gt;while&amp;lt;/tt&amp;gt;-Anweisung), ist nicht klar, welcher Befehl nach der Verzweigung ausgeführt werden soll, bevor Stufe 3 der Pipiline die Verzweigungsbedingung ausgewertet hat. Bis dahin wären die ersten beiden Stufen der Pipeline unbenutzt. Moderne Prozessoren benutzen zwar ausgefeilte Heuristiken, um das Ergebnis der Bedingung vorherzusagen, und führen den hoffentlich richtigen Zweig des Codes spekulativ aus, aber dies funktioniert nicht immer. Man sollte deshalb generell die Anzahl der Verzweigungen minimieren. Als Nebeneffekt führt dies meist auch zu besser lesbarem, verständlicherem Code. Im Matrixbeispiel kann man&lt;br /&gt;
       for j in range(N):&lt;br /&gt;
           jN = j*N&lt;br /&gt;
           for i in range(N):&lt;br /&gt;
               if i == j:&lt;br /&gt;
                    a[i + jN] = 1.0&lt;br /&gt;
               else:&lt;br /&gt;
                    a[i + jN] = 0.0&lt;br /&gt;
::durch&lt;br /&gt;
       for j in range(N):&lt;br /&gt;
           jN = j*N&lt;br /&gt;
           for i in range(N):&lt;br /&gt;
               a[i + jN] = 0.0&lt;br /&gt;
           a[j + jN] = 1.0&lt;br /&gt;
ersetzen. Die Diagonalelemente &amp;lt;tt&amp;gt;a[j + jN]&amp;lt;/tt&amp;gt; werden jetzt zwar zweimal initialisiert (in der Schleife auf Null, dann auf Eins), aber durch Elimination der &amp;lt;tt&amp;gt;if&amp;lt;/tt&amp;gt;-Abfrage wird dies wahrscheinlich mehr als ausgeglichen, zumal dadurch die innere Schleife wesentlich vereinfacht wurde.&lt;br /&gt;
;Ausnutzen des Prozessor-Cache: Zugriffe auf den Hauptspeicher sind sehr langsam. Deshalb werden stets ganze Speicherseiten auf einmal in den [http://en.wikipedia.org/wiki/Cache Cache] des Prozessors geladen. Wenn unmittelbar nacheinander benutzte Daten auch im Speicher nahe beieinander liegen (sogenannte &amp;quot;[http://en.wikipedia.org/wiki/Locality_of_reference locality of reference]&amp;quot;), ist die Wahrscheinlichkeit groß, dass die als nächstes benötigten Daten bereits im Cache sind und damit schnell gelesen werden können. Bei vielen Algorithmen kann man die Implementation so umordnen, dass die locality of reference verbessert wird, was zu einer drastischen Beschleunigung führt. Im Matrix-Beispiel ist z.B. die Reihenfolge der Schleifen wichtig. Für konstanten Index &amp;lt;tt&amp;gt;j&amp;lt;/tt&amp;gt; liegen die Indizes &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt; im Speicher hintereinander. Deshalb ist es günstig, in der inneren Schleife über &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt; zu iterieren:&lt;br /&gt;
       for j in range(N):&lt;br /&gt;
           jN = j*N&lt;br /&gt;
           for i in range(N):&lt;br /&gt;
               a[i + jN] = 0.0&lt;br /&gt;
           a[j + jN] = 1.0&lt;br /&gt;
:Die umgekehrte Reihenfolge der Schleifen ist hingegen ungünstig&lt;br /&gt;
       for i in range(N):&lt;br /&gt;
           for j in range(N):&lt;br /&gt;
               a[i + j*N] = 0.0&lt;br /&gt;
           a[i + i*N] = 1.0&lt;br /&gt;
:Jetzt werden in der inneren Schleife stets N Datenelemente übersprungen. Besonders bei großem N muss man daher häufig den Cache neu füllen, was bei der ersten Implementation nicht notwendig war.  (Ausserdem verliert man hier die Optimierung &amp;lt;tt&amp;gt;jN = j*N&amp;lt;/tt&amp;gt;, die jetzt nicht mehr möglich ist.)&lt;br /&gt;
&lt;br /&gt;
Als Faustregel kann man durch Optimierung eine Verdoppelung der Geschwindigkeit erreichen (in Ausnahmefällen auch mehr). Benötigt man stärkere Verbesserungen, muss man wohl oder übel einen besseren Algorithmus oder einen schnelleren Computer verwenden.&lt;br /&gt;
&lt;br /&gt;
== Algorithmen-Komplexität ==&lt;br /&gt;
&lt;br /&gt;
Komplexitätsbetrachtungen ermöglichen den Vergleich der prinzipiellen Eigenschaften von Algorithmen unabhängig von einer Implementation, Umgebung etc.&lt;br /&gt;
      &lt;br /&gt;
Eine einfache Möglichkeit ist das Zählen der Aufrufe einer Schlüsseloperation. Beispiel Sortieren:&lt;br /&gt;
* Anzahl der Vergleiche&lt;br /&gt;
* Anzahl der Vertauschungen&lt;br /&gt;
&lt;br /&gt;
=== Beispiel: Selection Sort ===&lt;br /&gt;
&lt;br /&gt;
  for i in range(len(a)-1):&lt;br /&gt;
    max = i&lt;br /&gt;
    for j in range(i+1, len(a)):&lt;br /&gt;
      if a[j] &amp;lt; a[max]:&lt;br /&gt;
        max = j&lt;br /&gt;
    a[max], a[i] = a[i], a[max]      # swap&lt;br /&gt;
&lt;br /&gt;
*Anzahl der Vergleiche: Ein Vergleich in jedem Durchlauf der inneren Schleife. Es ergibt sich folgende Komplexität:&lt;br /&gt;
*:Ingesamt &amp;lt;math&amp;gt;\sum_{i=0}^{N-2} \sum_{j=i+1}^{N-1}1 = \frac{N}{2} (N-1) \!&amp;lt;/math&amp;gt; Vergleiche.&lt;br /&gt;
&lt;br /&gt;
*Anzahl der Vertauschungen (swaps): Eine Vertauschung pro Durchlauf der äußeren Schleife:&lt;br /&gt;
*:Insgesamt &amp;lt;math&amp;gt;N-1 \!&amp;lt;/math&amp;gt; Vertauschungen&lt;br /&gt;
&lt;br /&gt;
Die Komplexität wird durch die Operationen bestimmt, die am häufigsten ausgeführt werden, hier also die Anzahl der Vergleiche. Die Anzahl der Vertauschungen ist hingegen kein geeignetes Kriterium für die Komplexität von selection sort, weil der Aufwand in der inneren Schleife ignoriert würde.&lt;br /&gt;
&lt;br /&gt;
=== Fallunterscheidung: Worst und Average Case ===&lt;br /&gt;
&lt;br /&gt;
Die Komplexität ist in der Regel eine Funktion der Eingabegröße (Anzahl der Eingabebits, Anzahl der Eingabeelemente). Sie kann aber auch von der Art der Daten abhängen, nicht nur von der Menge, z.B. vorsortierte Daten bei Quicksort. Um von der Art der Daten unabhängig zu werden, kann man zwei Fälle der Komplexität unterscheiden:&lt;br /&gt;
      &lt;br /&gt;
* Komplexität im ungünstigsten Fall &lt;br /&gt;
*: Der ungünstigste Fall ist die Eingabe gegebener Länge, für die der Algorithmus am langsamsten ist. Der Nachteil dieser Methode besteht darin, dass dieser ungünstige Fall in der Praxis vielleicht gar nicht oder nur selten vorkommt, so dass sich der Algorithmus in Wirklichkeit besser verhält als man nach dieser Analyse erwarten würde. Beim Quicksort-Algorithmus mit zufälliger Wahl des Pivot-Elements müsste z.B. stets das kleinste oder größte Element des aktuellen Intervalls als Pivot-Element gewählt werden, was äußerst unwahrscheinlich ist.&lt;br /&gt;
* Komplexität im durchschnittlichen/typischen Fall&lt;br /&gt;
*: Der typische Fall ist die mittlere Komplexität des Algorithmus über alle möglichen Eingaben. Dazu muss man die Wahrscheinlichkeit jeder möglichen Eingabe kennen, und berechnet dann die mittlere Laufzeit über dieser Wahrscheinlichkeitsverteilung. Leider ist die Wahrscheinlichkeit der Eingaben oft nicht bekannt, so dass man geeignete Annahmen treffen muss. Bei Sortieralgorithmen können z.B. alle möglichen Permutationen des Eingabearrays als gleich wahrscheinlich angenommen werden, und der typische Fall ist dann die mittlere Komplexität über alle diese Eingaben. Oft hat man jedoch in der Praxis andere Wahrscheinlichkeitsverteilungen, z.B. sind die Daten oft &amp;quot;fast sortiert&amp;quot; (nur wenige Elemente sind an der falschen Stelle). Dann verhält sich der Algorithmus ebenfalls anders als vorhergesagt.&lt;br /&gt;
&lt;br /&gt;
Wir beschränken uns in dieser Vorlesung auf die Komplexität im ungünstigseten Fall. '''Exakte''' Formeln für Komplexität sind aber auch dann schwer zu gewinnen, wie das folgende Beispiel zeigt:&lt;br /&gt;
&lt;br /&gt;
=== Beispiele aus den Übungen (Gemessene Laufzeiten für Mergesort/Selectionsort) ===&lt;br /&gt;
&lt;br /&gt;
* Mergesort: &amp;lt;math&amp;gt;\frac{0,977N\log N}{\log 2} + 0,267N-4.39 \!&amp;lt;/math&amp;gt;&lt;br /&gt;
*: andere Lösung: &amp;lt;math&amp;gt;1140 N\log(N) - 1819N + 6413 \!&amp;lt;/math&amp;gt;&lt;br /&gt;
* Selectionsort: &amp;lt;math&amp;gt;\frac{1}{2}N^2 - \frac{1}{2N} - 10^{-12} \!&amp;lt;/math&amp;gt;&lt;br /&gt;
*: andere Lösung: &amp;lt;math&amp;gt;1275N^2 - 116003^N + 11111144 \!&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Aus diesen Formeln wird nicht offensichtlich, welcher Algorithmus besser ist.&lt;br /&gt;
Näherung: Betrachte nur '''sehr große Eingaben''' (meist sind alle Algorithmen schnell genug für kleine Eingaben). Dieses Vorgehen wird als '''Asymptotische Komplexität''' bezeichnet (N gegen unendlich).&lt;br /&gt;
&lt;br /&gt;
=== Asymptotische Komplexität am Beispiel Polynom ===&lt;br /&gt;
&lt;br /&gt;
Polynom: &amp;lt;math&amp;gt;a\,x^2+b\,x+c=p\!&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;x \!&amp;lt;/math&amp;gt; sei die Eingabegröße, und wir betrachten die Entwicklung von &amp;lt;math&amp;gt;p \!&amp;lt;/math&amp;gt; in Abhängigkeit von &amp;lt;math&amp;gt;x \!&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;math&amp;gt;x=0 \!&amp;lt;/math&amp;gt;&lt;br /&gt;
*: &amp;lt;math&amp;gt;p=c \!&amp;lt;/math&amp;gt;&lt;br /&gt;
* &amp;lt;math&amp;gt;x=1 \!&amp;lt;/math&amp;gt;&lt;br /&gt;
*: &amp;lt;math&amp;gt;p=a+b+c \!&amp;lt;/math&amp;gt;&lt;br /&gt;
* &amp;lt;math&amp;gt;x=1000 \!&amp;lt;/math&amp;gt;&lt;br /&gt;
*: &amp;lt;math&amp;gt;p=1000000a+1000b+c \approx 1000000a\!&amp;lt;/math&amp;gt;&lt;br /&gt;
* &amp;lt;math&amp;gt;x \to \infty \!&amp;lt;/math&amp;gt;&lt;br /&gt;
*: &amp;lt;math&amp;gt;p \approx x^2a\!&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Für sehr große Eingaben verlieren also ''b'' und ''c'' immer mehr an Bedeutung, so dass am Ende nur noch ''a'' für die Komplexitätsbetrachtung wichtig ist.&lt;br /&gt;
&lt;br /&gt;
== Landau-Symbole ==&lt;br /&gt;
&lt;br /&gt;
Um die asymptotische Komplexität verschiedener Algorithmen miteinander vergleichen zu können, verwendet man die sogenannten [http://de.wikipedia.org/wiki/Landau-Symbole Landau-Symbole]. Das wichtigste Landau-Symbol ist &amp;lt;math&amp;gt;\mathcal{O}&amp;lt;/math&amp;gt;, mit dem man eine ''obere Schranke'' &amp;lt;math&amp;gt;f \in \mathcal{O}(g)&amp;lt;/math&amp;gt; für die Komplexität angeben kann. &lt;br /&gt;
&lt;br /&gt;
Schreibt man &amp;lt;math&amp;gt;f \in \Omega(g)&amp;lt;/math&amp;gt;, so stellt dies eine asymptotische ''untere Schranke'' für die Funktion f dar.&lt;br /&gt;
&lt;br /&gt;
Schließlich bedeutet &amp;lt;math&amp;gt;f \in \Theta(g)&amp;lt;/math&amp;gt;, dass die Funktion f genauso schnell wie die Funktion g wächst, das heißt man hat eine asymptotisch ''scharfe Schranke'' für f. Hierzu muss sowohl &amp;lt;math&amp;gt;f\in\mathcal{O}(g)&amp;lt;/math&amp;gt; als auch &amp;lt;math&amp;gt;f \in \Omega(g)&amp;lt;/math&amp;gt; erfüllt sein. &lt;br /&gt;
&lt;br /&gt;
Im nun folgenden soll auf die verschiedenen Landau-Symbole noch näher eingegeangen werden.&lt;br /&gt;
&lt;br /&gt;
===O-Notation===&lt;br /&gt;
&lt;br /&gt;
Intuitiv gilt: Für große N dominieren die am schnellsten wachsenden Terme einer Funktion. Die Notation &amp;lt;math&amp;gt;f \in \mathcal{O}(g)&amp;lt;/math&amp;gt; (sprich &amp;quot;f ist in O von g&amp;quot; oder &amp;quot;f ist von derselben Größenordnung wie g&amp;quot;) formalisiert eine solche Abschätzung der asymptotischen Komplexität der Funktion f von oben. &lt;br /&gt;
; Asymptotische Komplexität: Für zwei Funktionen f(x) und g(x) gilt&lt;br /&gt;
::&amp;lt;math&amp;gt;f(x) \in \mathcal{O}(g(x))&amp;lt;/math&amp;gt;&lt;br /&gt;
: genau dann wenn es eine Konstante &amp;lt;math&amp;gt;c&amp;gt;0&amp;lt;/math&amp;gt; und ein Argument &amp;lt;math&amp;gt;x_0&amp;lt;/math&amp;gt; gibt, so dass &lt;br /&gt;
::&amp;lt;math&amp;gt;\forall x \ge x_0:\quad f(x) \le c\,g(x)&amp;lt;/math&amp;gt;.&lt;br /&gt;
:Die Menge &amp;lt;math&amp;gt;\mathcal{O}(g(x))&amp;lt;/math&amp;gt; aller durch g(x) abschätzbaren Funktionen ist also formal definiert durch&lt;br /&gt;
::&amp;lt;math&amp;gt;\mathcal{O}(g(x)) = \{ f(x)\ |\ \exists c&amp;gt;0: \forall x \ge x_0: 0 \le f(x) \le c\,g(x)\}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Die Idee hinter dieser Definition ist, dass g(x) eine wesentlich einfachere Funktion ist als f(x), die sich aber nach geeigneter Skalierung (Multiplikation mit c) und für große Argumente x im wesentlichen genauso wie f(x) verhält. Man kann deshalb in der Algorithmenanalyse f(x) durch g(x) ersetzen. &amp;lt;math&amp;gt;f(x) \in \mathcal{O}(g(x))&amp;lt;/math&amp;gt; spielt für Funktionen eine ähnliche Rolle wie der Operator &amp;amp;le; für Zahlen: Falls a &amp;amp;le; b gilt, kann bei einer Abschätzung von oben ebenfalls a durch b ersetzt werden.&lt;br /&gt;
&lt;br /&gt;
==== Ein einfaches Beispiel ====&lt;br /&gt;
&lt;br /&gt;
[[Image:Sqsqrt.png]]&lt;br /&gt;
&lt;br /&gt;
Rot = &amp;lt;math&amp;gt;x^2 \!&amp;lt;/math&amp;gt;&lt;br /&gt;
Blau = &amp;lt;math&amp;gt;\sqrt{x} \!&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\sqrt{x} \in \mathcal{O}(x^2)\!&amp;lt;/math&amp;gt; weil &amp;lt;math&amp;gt;\sqrt{x} \le c\,x^2\!&amp;lt;/math&amp;gt; für alle &amp;lt;math&amp;gt;x \ge x_0 = 1 \!&amp;lt;/math&amp;gt; und &amp;lt;math&amp;gt;c = 1\!&amp;lt;/math&amp;gt;, oder auch für &amp;lt;math&amp;gt;x \ge x_0 = 4 \!&amp;lt;/math&amp;gt; und &amp;lt;math&amp;gt;c = 1/16&amp;lt;/math&amp;gt; (die Wahl von c und x&amp;lt;sub&amp;gt;0&amp;lt;/sub&amp;gt; in der Definition von O(.) ist beliebig, solange die Bedingungen erfüllt sind).&lt;br /&gt;
&lt;br /&gt;
==== Komplexität bei kleinen Eingaben ==== &lt;br /&gt;
&lt;br /&gt;
Algorithmus 1: &amp;lt;math&amp;gt;\mathcal{O}(N^2) \!&amp;lt;/math&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
Algorithmus 2: &amp;lt;math&amp;gt;\mathcal{O}(N\log{N}) \!&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Algorithmus 2 ist schneller (von geringerer Komplexität) für große Eingaben, aber bei kleinen Eingaben (insbesondere, wenn der Algorithmus in einer Schleife immer wieder mit kleinen Eingaben aufgerufen wird) könnte Algorithmus 1 schneller sein, falls der in der &amp;lt;math&amp;gt;\mathcal{O}&amp;lt;/math&amp;gt;-Notation verborgene konstante Faktor ''c'' bei Algorithmus 2 einen wesentlich größeren Wert hat als bei Algorithmus 1.&lt;br /&gt;
&lt;br /&gt;
==== Eigenschaften der O-Notation (Rechenregeln) ==== &lt;br /&gt;
&lt;br /&gt;
# Transitiv:&lt;br /&gt;
#: &amp;lt;math&amp;gt;f(x) \in \mathcal{O}(g(x)) \land g(x) \in \mathcal{O}(h(x)) \to f(x) \in \mathcal{O}(h(x)) \!&amp;lt;/math&amp;gt;           &lt;br /&gt;
# Additiv:&lt;br /&gt;
#: &amp;lt;math&amp;gt;f(x) \in \mathcal{O}(h(x)) \land g(x) \in \mathcal{O}(h(x)) \to f(x) + g(x) \in \mathcal{O}(h(x)) \!&amp;lt;/math&amp;gt;           &lt;br /&gt;
# Für Monome gilt:&lt;br /&gt;
#: &amp;lt;math&amp;gt;x^k \in \mathcal{O}(x^k)&amp;lt;/math&amp;gt; und&lt;br /&gt;
#: &amp;lt;math&amp;gt;x^k \in \mathcal{O}(x^{k+j}), \forall j \ge 0 \!&amp;lt;/math&amp;gt;&lt;br /&gt;
# Multiplikation mit einer Konstanten:&lt;br /&gt;
#: &amp;lt;math&amp;gt;f(x) \in \mathcal{O}(g(x)) \to c\,f(x) \in \mathcal{O}(g(x))\!&amp;lt;/math&amp;gt;&lt;br /&gt;
#: andere Schreibweise:&lt;br /&gt;
#: &amp;lt;math&amp;gt;f(x) = c\,g(x) \to f(x) \in \mathcal{O}(g(x))\!&amp;lt;/math&amp;gt;&lt;br /&gt;
# Folgerung aus 3. und 4. für Polynome: &lt;br /&gt;
#: &amp;lt;math&amp;gt;a_0+a_1\,x + ... + a_n\,x^n \in \mathcal{O}(x^n)\!&amp;lt;/math&amp;gt;&lt;br /&gt;
#: Beispiel: &amp;lt;math&amp;gt;a\,x^2+b\,x+c \in \mathcal{O}(x^2)\!&amp;lt;/math&amp;gt;&lt;br /&gt;
# Logarithmus:&lt;br /&gt;
#: &amp;lt;math&amp;gt;a, b &amp;gt; 1\!&amp;lt;/math&amp;gt;&lt;br /&gt;
#: &amp;lt;math&amp;gt;\log_{a}{x} \in \mathcal{O}(\log_{b}{x})\!&amp;lt;/math&amp;gt;&lt;br /&gt;
#: Die Basis des Logarithmus spielt also keine Rolle.&lt;br /&gt;
#: Beweis hierfür:&lt;br /&gt;
#:: &amp;lt;math&amp;gt;\log_{a}{x} = \frac{\log_{b}{x}}{\log_{b}{a}}\!&amp;lt;/math&amp;gt;&lt;br /&gt;
#:: Mit &amp;lt;math&amp;gt;c = 1 / \log_{b}{a}\,&amp;lt;/math&amp;gt; gilt: &amp;lt;math&amp;gt;\log_{a}{x} = c\,\log_{b}{x}\!&amp;lt;/math&amp;gt;.&lt;br /&gt;
#:: Wird hier die (zweite) Regel für Multiplikation mit einer Konstanten angewendet, fällt der konstante Faktor weg, also &amp;lt;math&amp;gt;\log_{a}{x} \in \mathcal{O}(\log_{b}{x})\!&amp;lt;/math&amp;gt;.&lt;br /&gt;
#: Insbesondere gilt auch &amp;lt;math&amp;gt;\log_{a}{x} \in \mathcal{O}(\log_{2}{x})\!&amp;lt;/math&amp;gt;, es kann also immer der 2er Logarithmus verwendet werden.&lt;br /&gt;
&lt;br /&gt;
==== O-Kalkül ==== &lt;br /&gt;
&lt;br /&gt;
Das O-Kalkül definiert wichtige Vereinfachungsregeln for Ausdrücke in O-Notation (Beweise: siehe Übungsaufgabe):&lt;br /&gt;
&lt;br /&gt;
# &amp;lt;math&amp;gt;f(x) \in \mathcal{O}(f(x))\!&amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt;\mathcal{O}(\mathcal{O}(f(x))) \in \mathcal{O}(f(x))\!&amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt;c\,\mathcal{O}(f(x)) \in \mathcal{O}(f(x))\,&amp;lt;/math&amp;gt; für jede Konstante ''c''&lt;br /&gt;
# &amp;lt;math&amp;gt;\mathcal{O}(f(x))+c \in \mathcal{O}(f(x))\,&amp;lt;/math&amp;gt; für jede Konstante ''c''&lt;br /&gt;
# Sequenzregel:&lt;br /&gt;
#: Wenn zwei nacheinander ausgeführte Programmteile die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(f(x))&amp;lt;/math&amp;gt; bzw. &amp;lt;math&amp;gt;\mathcal{O}(g(x))&amp;lt;/math&amp;gt; haben, gilt für beide gemeinsam:&lt;br /&gt;
#: &amp;lt;math&amp;gt;\mathcal{O}(f(x)) + \mathcal{O}(g(x)) \in \mathcal{O}(f(x))&amp;lt;/math&amp;gt; falls &amp;lt;math&amp;gt;g(x) \in \mathcal{O}(f(x))&amp;lt;/math&amp;gt; bzw.&lt;br /&gt;
#: &amp;lt;math&amp;gt;\mathcal{O}(f(x)) + \mathcal{O}(g(x)) \in \mathcal{O}(g(x))\!&amp;lt;/math&amp;gt; falls &amp;lt;math&amp;gt;f(x) \in \mathcal{O}(g(x))&amp;lt;/math&amp;gt;.&lt;br /&gt;
#: Informell schreibt man auch: &amp;lt;math&amp;gt;\mathcal{O}(f(x)) + \mathcal{O}(g(x)) \in \mathcal{O}(max(f(x), g(x)))\!&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Schachtelungsregel bzw. Aufrufregel:&lt;br /&gt;
#: Wenn in einer geschachtelten Schleife die äußere Schleife die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(f(x))&amp;lt;/math&amp;gt; hat, und die innere &amp;lt;math&amp;gt;\mathcal{O}(g(x))&amp;lt;/math&amp;gt;, gilt für beide gemeinsam:&lt;br /&gt;
#: &amp;lt;math&amp;gt;\mathcal{O}(f(x)) * \mathcal{O}(g(x)) \in \mathcal{O}(f(x) * g(x))\!&amp;lt;/math&amp;gt;.&lt;br /&gt;
#: Gleiches gilt wenn eine Funktion &amp;lt;math&amp;gt;\mathcal{O}(f(x))&amp;lt;/math&amp;gt;-mal aufgerufen wird, und die Komplexität der Funktion selbst &amp;lt;math&amp;gt;\mathcal{O}(g(x))&amp;lt;/math&amp;gt; ist.&lt;br /&gt;
&lt;br /&gt;
;Beispiel für 5.: Beide Schleifen haben die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt;. Dies gilt auch für ihre Hintereinanderausführung:&lt;br /&gt;
      for i in range(N):&lt;br /&gt;
          a[i] = i&lt;br /&gt;
      for i in range(N):&lt;br /&gt;
          print a[i]&lt;br /&gt;
;Beispiele für 6.: Beide Schleifen haben die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt;. Ihre Verschachtelung hat daher die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(N^2)&amp;lt;/math&amp;gt;. &lt;br /&gt;
      for i in range(N):&lt;br /&gt;
          for j in range(N):&lt;br /&gt;
              a[i*N + j] = i+j&lt;br /&gt;
: Dies gilt ebenso, wenn statt der inneren Schleife eine Funktion mit Komplexität &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; ausgeführt wird:&lt;br /&gt;
      for i in range(N):&lt;br /&gt;
          a[i] = foo(i, N)  # &amp;lt;math&amp;gt;\mathrm{foo}(i, N) \in \mathcal{O}(N)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== O-Kalkül auf das Beispiel des Selectionsort angewandt ==== &lt;br /&gt;
&lt;br /&gt;
Selectionsort: Wir hatten gezeigt dass &amp;lt;math&amp;gt;f(N) = \frac{N^2}{2} - \frac{N}{2}&amp;lt;/math&amp;gt;. Nach der Regel für Polynome vereinfacht sich dies zu &amp;lt;math&amp;gt;f(N) \in \mathcal{O}\left(\frac{N^2}{2}\right) \in \mathcal{O}(N^2)\!&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Alternativ via Schachtelungsregel:&lt;br /&gt;
: Die äußere Schleife wird (''N''-1)-mal durchlaufen: &amp;lt;math&amp;gt;N-1 \in \mathcal{O}(N)&amp;lt;/math&amp;gt;&lt;br /&gt;
: Die innere Schleife wird (''N-i''-1)-mal durchlaufen. Das sind im Mittel ''N''/2 Durchläufe: &amp;lt;math&amp;gt;N/2 \in \mathcal{O}(N)&amp;lt;/math&amp;gt;&lt;br /&gt;
: Zusammen: &amp;lt;math&amp;gt;\mathcal{O}(N)*\mathcal{O}(N) \in \mathcal{O}(N^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nach beiden Vorgehensweisen kommen wir zur Schlussfolgerung, dass der Selectionsort die asymptotische Komplexität &amp;lt;math&amp;gt;\mathcal{O}(N^2)\!&amp;lt;/math&amp;gt; besitzt.&lt;br /&gt;
&lt;br /&gt;
==== Zusammenhang zwischen Komplexität und Laufzeit ==== &lt;br /&gt;
&lt;br /&gt;
Wenn eine Operation 1ms dauert, erreichen Algorithmen verschiedener Komplexität folgende Leistungen (wobei angenommen wird, dass der in der &amp;lt;math&amp;gt;\mathcal{O}&amp;lt;/math&amp;gt;-Notation verborgene konstante Faktor immer etwa gleich 1 ist):&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align:left&amp;quot; cellpadding=&amp;quot;7&amp;quot;&lt;br /&gt;
|+&lt;br /&gt;
|-&lt;br /&gt;
! Komplexität !! Operationen in 1s !! Operationen in 1min !! Operationen in 1h&lt;br /&gt;
|-&lt;br /&gt;
! &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt;&lt;br /&gt;
| 1000 || 60.000 || 3.600.000&lt;br /&gt;
|-&lt;br /&gt;
! &amp;lt;math&amp;gt;\mathcal{O}(N\log_2{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
| 140 || 4895 || 204094&lt;br /&gt;
|-&lt;br /&gt;
! &amp;lt;math&amp;gt;\mathcal{O}(N^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
| 32 || 245 || 1898&lt;br /&gt;
|-&lt;br /&gt;
! &amp;lt;math&amp;gt;\mathcal{O}(N^3)&amp;lt;/math&amp;gt;&lt;br /&gt;
| 10 || 39 || 153&lt;br /&gt;
|-&lt;br /&gt;
! &amp;lt;math&amp;gt;\mathcal{O}(2^N)&amp;lt;/math&amp;gt;&lt;br /&gt;
| 10 || 16 || 21&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==== Exponentielle Komplexität ==== &lt;br /&gt;
Der letzte Fall &amp;lt;math&amp;gt;\mathcal{O}(2^N)&amp;lt;/math&amp;gt; ist von exponentieller Komplexität. Das bedeutet, dass eine Verdopplung des Aufwands nur bewirkt, dass die maximale Problemgröße um eine Konstante wächst. Algorithmen mit exponentieller (oder noch höherer) Komplexität werden deshalb als '''ineffizient''' bezeichnet. Algorithmen mit höchstens polynomieller Komplexität gelten hingegen als effizient.&lt;br /&gt;
&lt;br /&gt;
In der Praxis sind allerdings auch polynomielle Algorithmen mit hohem Exponenten meist zu langsam. Als Faustregel kann man eine praktische Grenze von &amp;lt;math&amp;gt;\mathcal{O}(N^3)&amp;lt;/math&amp;gt; ansehen. Bei einer Komplexität von &amp;lt;math&amp;gt;\mathcal{O}(N^3)&amp;lt;/math&amp;gt; bewirkt ein verdoppelter Aufwand immer noch eine Steigerung der maximalen Problemgröße um den Faktor &amp;lt;math&amp;gt;\sqrt[3]{2}&amp;lt;/math&amp;gt; (also eine ''multiplikative'' Vergrößerung um ca. 25%, statt nur einer additiven Vergrößerung wie bei exponentieller Komplexität).&lt;br /&gt;
&lt;br /&gt;
===&amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;- Notation===&lt;br /&gt;
&lt;br /&gt;
Genauso wie &amp;lt;math&amp;gt;f \in \mathcal{O}(g)&amp;lt;/math&amp;gt; eine Art &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt;-Operator für Funktionen ist, definiert &amp;lt;math&amp;gt;f \in \Omega(g) &amp;lt;/math&amp;gt; eine Abschätzung von unten, analog zum &amp;lt;math&amp;gt;\ge&amp;lt;/math&amp;gt;-Operator für Zahlen. Formal kann man &amp;lt;math&amp;gt;f(N) \in \Omega(g(N)) &amp;lt;/math&amp;gt; genau dann schreiben, falls es eine Konstante &amp;lt;math&amp;gt; c &amp;gt; 0 &amp;lt;/math&amp;gt; gibt, so dass &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; f(N) \ge c \cdot g(N) &amp;lt;/math&amp;gt; für &amp;lt;math&amp;gt; N  \ge N_0 &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
gilt.&lt;br /&gt;
Man verwendet diese Notation also um abzuschätzen, wie groß der Aufwand (die Komplexität) für einen bestimmten Algorithmus ''mindestens'' ist und nicht ''höchstens'', was man mit der &amp;lt;math&amp;gt;\mathcal{O}&amp;lt;/math&amp;gt; - Notation ausdrücken würde.&lt;br /&gt;
&lt;br /&gt;
Ein praktisches Beispiel für eine Anwendung der &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;- Notation wäre die Fragestellung, ob es ''prinzipiell'' einen besseren Algorithmus für ein bestimmtes Problem gibt. Wie später im Abschnitt [[Suchen#Sortieren_als_Suchproblem|Sortieren als Suchproblem]] gezeigt wird, ist das Sortieren eines Arrays durch paarweise Vergleiche von Elementen immer mindestens von der Komplexität &amp;lt;math&amp;gt; \Omega(N\cdot \ln N) &amp;lt;/math&amp;gt;, was konkret bedeutet, dass kein Sortieralgorithmus, der nach diesem Prinzip arbeitet, jemals eine geringere Komplexität als beispielsweise Merge-Sort haben wird. Natürlich kann man den entsprechenden Sortieralgorithmus, also Merge-Sort zum Beispiel, unter Umständen noch optimieren, aber die Komplexität wird erhalten bleiben. Mit diesem Wissen kann man sich viel (vergebliche) Arbeit sparen.&lt;br /&gt;
&lt;br /&gt;
===&amp;lt;math&amp;gt;\Theta&amp;lt;/math&amp;gt;- Notation===&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;math&amp;gt;f(N) \in \Theta(g(N))&amp;lt;/math&amp;gt; ist eine scharfe Abschätzung der asymptotischen Komplexität einer Funktion f. &lt;br /&gt;
&lt;br /&gt;
Damit dies gilt, muss &amp;lt;math&amp;gt;f(N) \in \mathcal{O}(g(N))&amp;lt;/math&amp;gt; und ''gleichzeitig'' &amp;lt;math&amp;gt;f(N) \in \Omega(g(N))&amp;lt;/math&amp;gt; erfüllt sein.&lt;br /&gt;
&lt;br /&gt;
Dies ist natürlich auch die beste Abschätzung der asymptotischen Komplexität einer Funktion f. Formal bedeutet &amp;lt;math&amp;gt;f(N) \in \Theta(g(N))&amp;lt;/math&amp;gt; dass es zwei Konstanten &amp;lt;math&amp;gt; c_1 &amp;lt;/math&amp;gt; und &amp;lt;math&amp;gt; c_2 &amp;lt;/math&amp;gt;, beide größer als Null, gibt, so dass für alle &amp;lt;math&amp;gt; N \geq N_0 &amp;lt;/math&amp;gt; gilt: &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; c_1 \cdot g(N) \leq f(N) \leq c_2 \cdot g(N) &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
In der Praxis wird manchmal statt der &amp;lt;math&amp;gt;\Theta&amp;lt;/math&amp;gt;-Notation auch dann die &amp;lt;math&amp;gt;\mathcal{O}&amp;lt;/math&amp;gt;-Notation benutzt, wenn eine scharfe Schranke ausgedrückt werden soll. Dies ist zwar formal nicht korrekt, aber man kann die intendierte Bedeutung meist aus dem Kontext erschließen.&lt;br /&gt;
&lt;br /&gt;
== Komplexitätsvergleich zweier Algorithmen ==&lt;br /&gt;
&lt;br /&gt;
In diesem Abschnitt wollen wir der Frage nachgehen, wie ein formaler Beweis für die Behauptung &amp;lt;math&amp;gt; f(N) \in \mathcal{O}(g(N))&amp;lt;/math&amp;gt; geschehen kann. Hierbei werden zwei Beweismethoden vorgestellt werden, und zwar der '''Beweis über die Definition der Komplexität''' sowie der '''Beweis durch Dividieren'''.&lt;br /&gt;
&lt;br /&gt;
===Beweis über die Definition der asymptotischen Komplexität===&lt;br /&gt;
&lt;br /&gt;
Die Definition der asymptotischen Komplexität &amp;lt;math&amp;gt;f(N) \in \mathcal{O}(g(N))&amp;lt;/math&amp;gt; war: &lt;br /&gt;
&lt;br /&gt;
Es gibt eine Konstante &amp;lt;math&amp;gt; c &amp;gt; 0 &amp;lt;/math&amp;gt;, so dass &amp;lt;math&amp;gt; f(N) \le c \cdot g(N) &amp;lt;/math&amp;gt; für &amp;lt;math&amp;gt; N  \ge N_0 &amp;lt;/math&amp;gt; erfüllt ist. &lt;br /&gt;
&lt;br /&gt;
Um also die die asymptotische Komplexität &amp;lt;math&amp;gt;f(N) \in \mathcal{O}(g(N))&amp;lt;/math&amp;gt; zu beweisen, muss man die oben erwähnten Konstanten c und &amp;lt;math&amp;gt; N_0 &amp;lt;/math&amp;gt; finden, so dass &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; f(N) \leq c \cdot g(N) &amp;lt;/math&amp;gt; für alle &amp;lt;math&amp;gt; N \ge N_0 &amp;lt;/math&amp;gt; erfüllt ist. &lt;br /&gt;
&lt;br /&gt;
Dies geschieht zweckmäßigerweise mit dem Beweisprinzip der ''vollständigen Induktion''. Hierbei ist zu zeigen, dass&lt;br /&gt;
# &amp;lt;math&amp;gt; f(N_0) \leq g(N_0) &amp;lt;/math&amp;gt; für die eine zu bestimmende Konstante &amp;lt;math&amp;gt; N_0 &amp;lt;/math&amp;gt; gilt (''Induktionsanfang'') und &lt;br /&gt;
# falls &amp;lt;math&amp;gt; f(N) \leq g(N) &amp;lt;/math&amp;gt;, dann auch &amp;lt;math&amp;gt; f(N+1) \leq g(N+1) &amp;lt;/math&amp;gt; (''Induktionsschritt'') gilt.&lt;br /&gt;
&lt;br /&gt;
===Beweis durch Dividieren===&lt;br /&gt;
&lt;br /&gt;
Hierbei wählt man eine Konstante c und zeigt, dass &amp;lt;math&amp;gt; \lim_{N \rightarrow \infty} \frac{f(N)}{c \cdot g(N)} \leq 1 &amp;lt;/math&amp;gt; gilt (für die O-Notation, bei &amp;amp;Omega;-Notation gilt entsprechend &amp;lt;math&amp;gt;\geq 1 &amp;lt;/math&amp;gt;). Man kann dies auch als alternative Definition der Komplexität verwenden.&lt;br /&gt;
&lt;br /&gt;
Als Beispiel betrachten wir die beiden Funktionen &amp;lt;math&amp;gt; f(N) = N \,\lg N &amp;lt;/math&amp;gt; und &amp;lt;math&amp;gt; g(N) = N^2 &amp;lt;/math&amp;gt; und wollen zeigen, dass &amp;lt;math&amp;gt;f(N) \in \mathcal{O}(g(N))&amp;lt;/math&amp;gt; gilt. &lt;br /&gt;
&lt;br /&gt;
Als Konstante c wählen wir &amp;lt;math&amp;gt; c = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \lim_{N \rightarrow \infty} \frac{f(N)}{g(N)} = \lim_{N \rightarrow \infty} \frac{\lg N}{N} = \frac{\infty}{\infty} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Unbestimmte Ausdrücke der Form &lt;br /&gt;
&amp;lt;math&amp;gt; \lim_{x \rightarrow x_0} \frac{f(x)}{g(x)} &amp;lt;/math&amp;gt;,&lt;br /&gt;
in denen sowohl &amp;lt;math&amp;gt; f(x) &amp;lt;/math&amp;gt; als auch &amp;lt;math&amp;gt; g(x) &amp;lt;/math&amp;gt; mit &amp;lt;math&amp;gt; x \rightarrow x_0 &amp;lt;/math&amp;gt; gegen Null oder gegen Unendlich streben, kann man manchmal mit den Regeln von [http://de.wikipedia.org/wiki/L%27Hospital%27sche_Regel ''l'Hospital''] berechnen. Danach darf man die Funktionen f und g zur Berechnung des unbestimmten Ausdrucks durch ihre k-ten Ableitungen ersetzen:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \lim_{x \rightarrow x_0} \frac{f(x)}{g(x)} = \lim_{x \rightarrow x_0} \frac{f^{(k)}(x)}{g^{(k)}(x)} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In unserem Fall verwenden wir die erste Ableitung und erhalten:&lt;br /&gt;
&amp;lt;math&amp;gt; \lim_{N \rightarrow \infty} \frac{f'(x)}{g'(x)} = \lim_{N \rightarrow \infty} \frac{1/N}{1} \rightarrow 0 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Damit wurde &amp;lt;math&amp;gt;f(N) \in \mathcal{O}(g(N))&amp;lt;/math&amp;gt;, also &amp;lt;math&amp;gt;N \lg N \in \mathcal{O}(N^2)&amp;lt;/math&amp;gt; gezeigt.&lt;br /&gt;
&lt;br /&gt;
Man beachte hierbei, dass &amp;lt;math&amp;gt;N \lg N \in \mathcal{O}(N^2)&amp;lt;/math&amp;gt; keine enge Grenze für die Komplexität von &amp;lt;math&amp;gt;N \,\lg N&amp;lt;/math&amp;gt; darstellt, da der Grenzwert &amp;lt;math&amp;gt; \lim_{N \rightarrow \infty} \frac{f'(x)}{g'(x)}\, &amp;lt;/math&amp;gt; gegen 0 und nicht gegen eine von Null verschiedene Konstante strebt. In diesem Fall haben wir die Komplexität von &amp;lt;math&amp;gt;N \cdot \lg N &amp;lt;/math&amp;gt; also nur nach oben abschätzen können.&lt;br /&gt;
&lt;br /&gt;
===Beispiel für den Komplexitätsvergleich: Gleitender Mittelwert (Running Average)===&lt;br /&gt;
&lt;br /&gt;
Wir berechnen für ein gegebenes Array &amp;lt;tt&amp;gt;a&amp;lt;/tt&amp;gt; einen gleitenden Mittelwert über &amp;lt;tt&amp;gt;k&amp;lt;/tt&amp;gt; Elemente:&amp;lt;br/&amp;gt;&lt;br /&gt;
::&amp;lt;math&amp;gt;r_i = \frac{1}{k} \sum_{j=i-k+1}^i a_j&amp;lt;/math&amp;gt; &amp;lt;br/&amp;gt;&lt;br /&gt;
Das heisst, für jedes &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt; mitteln wir die letzten &amp;lt;tt&amp;gt;k&amp;lt;/tt&amp;gt; Elemente von &amp;lt;tt&amp;gt;a&amp;lt;/tt&amp;gt; und schreiben das Ergebnis in &amp;lt;tt&amp;gt;r[i]&amp;lt;/tt&amp;gt;. Diese Operation ist z.B. bei Börsenkursen wichtig: Neben dem aktuellen Kurs für jeden Tag wird dort meist auch der gleitende Mittelwert der letzten 30 Tage sowie der letzten 200 Tage angegeben. In diesen Mittelwerten erkennt man besser die langfristige Tendenz, weil die täglichen Schwankungen herausgemittelt werden. Wir nehmen außerdem an, dass&lt;br /&gt;
* Array-Zugriff hat eine Komplexit&amp;amp;auml;t von O(1)&lt;br /&gt;
* &amp;lt;math&amp;gt;k \ll N&amp;lt;/math&amp;gt;, d.h. &amp;lt;math&amp;gt;N-k\approx N&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Die beiden folgenden Algorithmen berechnen die Mittelwerte auf unterschiedliche Art. Der linke folgt der obigen Definition durch eine Summe, während der rechte inkrementell arbeitet: Man kann den Bereich der &amp;lt;tt&amp;gt;k&amp;lt;/tt&amp;gt; letzten Werte als Fenster betrachten, das über das Array &amp;lt;tt&amp;gt;a&amp;lt;/tt&amp;gt; geschoben wird. Schiebt man das Fenster ein Element weiter, fällt links ein Element heraus, und rechts kommt eins hinzu. Man muss also nicht jedes Mal die Summe neu berechnen, sondern kann den vorigen Wert aktualisieren. Wir werden sehen, dass dies Folgen für die Komplexität des Algorithmus hat.&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;0&amp;quot; cellpadding=&amp;quot;2&amp;quot; &lt;br /&gt;
|-&lt;br /&gt;
! Programmzeile&lt;br /&gt;
! Version 1: O(N * k)&lt;br /&gt;
! Komplexit&amp;amp;auml;t&lt;br /&gt;
! Version 2: O(N)&lt;br /&gt;
! Komplexit&amp;amp;auml;t&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
1.&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;tt&amp;gt;r = [0] * len(a)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(N)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;tt&amp;gt;r = [0] * len(a)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(N)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
2.&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;tt&amp;gt;if k &amp;gt; len(a):&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(1)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;tt&amp;gt;if k &amp;gt; len(a):&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(1)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
3.&lt;br /&gt;
|&lt;br /&gt;
:: &amp;lt;tt&amp;gt;raise RuntimeError (&amp;quot;k zu gro&amp;amp;szlig;&amp;quot;)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
|&lt;br /&gt;
:: &amp;lt;tt&amp;gt;raise RuntimeError (&amp;quot;k zu gro&amp;amp;szlig;&amp;quot;)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
4.&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;tt&amp;gt;for j in range(k-1, len(a)):&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;center&amp;gt;O(N-k+1) = '''O(N)'''&amp;lt;/center&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;tt&amp;gt;for i in range(k):&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(k)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
5.&lt;br /&gt;
|&lt;br /&gt;
:: &amp;lt;tt&amp;gt;for i in range(j-k+1, j+1):&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(k)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|&lt;br /&gt;
:: &amp;lt;tt&amp;gt;r[k-1] += a[i]&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(1)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
6.&lt;br /&gt;
|&lt;br /&gt;
:::: &amp;lt;tt&amp;gt;r[j] += a[i]&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(1)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;tt&amp;gt;for j in range(k, len(a)):&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;center&amp;gt;O(N-k+1) = '''O(N)'''&amp;lt;/center&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
7.&lt;br /&gt;
|&lt;br /&gt;
:: &amp;lt;tt&amp;gt;r[j] /= float(k)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(1)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|&lt;br /&gt;
:: &amp;lt;tt&amp;gt;r[j] = (a[j] - a[j-k] + r[j-1])&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(1)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
8.&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;tt&amp;gt;return r&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(1)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;tt&amp;gt;for j in range(len(a)):&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(N)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
9.&lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
|&lt;br /&gt;
:: &amp;lt;tt&amp;gt;r[j] /= float(k)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(1)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
10.&lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;tt&amp;gt;return r&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(1)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Wir zeigen unten dass Version 2 eine geringere Komplexit&amp;amp;auml;t besitzt, obwohl sie mehr Zeilen ben&amp;amp;ouml;tigt.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Wir haben in der Tabelle die Komplexität jeder Zeile für sich angegeben. Einfache Anweisungen (Berechnungen, Lese- und Schreibzugriffe auf das Array, Zuweiseungen) haben konstante Komplexität, die Komplexität des Schleifenkopfes allein (also der &amp;lt;tt&amp;gt;for&amp;lt;/tt&amp;gt;-Anweisung ohne den eingerückten Schleifenkörper) entspricht der Anzahl der Durchläufe. Wir müssen jetzt noch die Verschachtelung der Schleifen und die Nacheinanderausführung von Anweisungen berücksichtigen. &lt;br /&gt;
&lt;br /&gt;
====Berechnung der Komplexität von Version 1====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;small&amp;gt;(Wiederholung der Rechenregeln: siehe Abschnitt [[Effizienz#O-Notation|O-Notation]])&amp;lt;/small&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Wir betrachten zunächst die innere Schleife (Zeilen 5 und 6 von Version 1):&lt;br /&gt;
&lt;br /&gt;
Der Schleifenkopf (Zeile 5) hat die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(k)&amp;lt;/math&amp;gt;, weil die Schleife k-mal durchlaufen wird. Der Schleifenkörper (Zeile 6) hat die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;. Nach der Verschachtelungsregel müssen wir die beiden Komplexitäten multiplizieren, und es ergibt sich:&lt;br /&gt;
&lt;br /&gt;
::&amp;lt;math&amp;gt;\mathcal{O}(k)\cdot\mathcal{O}(1) = \mathcal{O}(k\cdot 1)=\mathcal{O}(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Wir betrachten nun die äußere Schleife. Der Schleifenkopf (Zeile 4) wird (N-k)-mal durchlaufen und hat somit eine Komplexität von &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt;. Der Schleifenkörper (Zeilen 5 bis 7) besteht aus der inneren Schleife (Zeilen 5 und 6) mit der gerade berechneten Komplexität &amp;lt;math&amp;gt;\mathcal{O}(k)&amp;lt;/math&amp;gt; sowie einer einfachen Anweisung (Zeile 7) mit Komplexität &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;. Nach der Sequenzregel wird die Komplexität des Schleifenkörpers durch Addition berechnet:&lt;br /&gt;
&lt;br /&gt;
::&amp;lt;math&amp;gt;\mathcal{O}(k)+\mathcal{O}(1) = \mathcal{O}(\max(k,1)) = \mathcal{O}(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Die Komplexität der gesamten äußeren Schleife erhalten wir nach der Verschachtelungsregel wieder durch multiplizieren:&lt;br /&gt;
&lt;br /&gt;
::&amp;lt;math&amp;gt;\mathcal{O}(N)\cdot\mathcal{O}(k) = \mathcal{O}(N\cdot k)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Die übrigen Schritte des Algorithmus werden einfach nacheinander ausgeführt, so dass sie ebenfalls nach der Sequenzregel behandelt werden. Wir erhalten&lt;br /&gt;
&lt;br /&gt;
::&amp;lt;math&amp;gt;\mathcal{O}(N)+\mathcal{O}(1)+\mathcal{O}(N\cdot k)+\mathcal{O}(1) = \mathcal{O}(\max(N,1,N\cdot k,1)) = \mathcal{O}(N\cdot k)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Der gesamte Algorithmus hat also die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(N\cdot k)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
====Berechnung der Komplexität von Version 2====&lt;br /&gt;
&lt;br /&gt;
Hier gibt es nur einfache Schleifen ohne Verschachtelung. Da der Schleifenkörper jeder Schleife nur einfache Anweisungen der Komplexität &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; enthält, ergibt sich die Komplexität der Schleifen nach der Verschachtelungsregel als&lt;br /&gt;
&lt;br /&gt;
::&amp;lt;math&amp;gt;\mathcal{O}(X)\cdot\mathcal{O}(1) = \mathcal{O}(X\cdot 1)=\mathcal{O}(X)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
wobei &amp;lt;math&amp;gt;\mathcal{O}(X)&amp;lt;/math&amp;gt; die Komplexität des jeweiligen Schleifenkopfes ist. Wir erhalten also für Zeilen 4 und 5: &amp;lt;math&amp;gt;\mathcal{O}(k)&amp;lt;/math&amp;gt;, Zeilen 6 und 7: &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt;, Zeilen 8 und 9: &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt;. Die Hintereinanderausführung wird nach der Sequenzregel behandelt:&lt;br /&gt;
&lt;br /&gt;
::&amp;lt;math&amp;gt;\mathcal{O}(N)+\mathcal{O}(1)+\mathcal{O}(k)+\mathcal{O}(N)+\mathcal{O}(N)+\mathcal{O}(1) = \mathcal{O}(\max(N,1,k,N,N,1)) = \mathcal{O}(N)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Dieser Algorithmus hat also nur die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
====Fazit====&lt;br /&gt;
&lt;br /&gt;
Obwohl Version 2 mehr Schritte benötigt hat sie eine geringere Komplexität, da die for-Schleifen nicht wie bei Version 1 verschachtelt/untergeordnet sind. Bei verschachtelten for-Schleifen muss die Multiplikationsregel angewendet werden &amp;amp;rarr; höhere Komplexität.&lt;br /&gt;
&lt;br /&gt;
Die gerade berechnete Komplexität gilt aber &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; unter der Annahme, dass Array-Zugriffe konstante Komplexität &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; besitzen. Wenn dies nicht der Fall ist, kann sich die Komplexität des Algorithmus drastisch verschlechtern.&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;0&amp;quot; cellpadding=&amp;quot;5&amp;quot; &lt;br /&gt;
|Allgemein gilt:&amp;lt;br/&amp;gt;&lt;br /&gt;
Algorithmen-Analysen beruhen auf der Annahme, dass Zugriffe auf die Daten optimal schnell sind, dass heißt, dass die für den jeweiligen Algorithmus am besten geeignete Datenstruktur verwendetet wird.&amp;lt;br /&amp;gt; &amp;amp;rarr; Ansonsten: Komplexitätsverschlechterung!&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Beispiel für eine Verschlechterung der Komplexität durch Verwendung einer nicht optimalen Datenstruktur====&lt;br /&gt;
&lt;br /&gt;
Wir verwende im Mittelwert-Algorithmus eine verkettete Liste anstelle des Eingabe-Arrays a&amp;lt;/u&amp;gt;. Wir benötigen dazu eine Funktion, die das j-te Element der Liste zurückgibt. Wie üblich ist die Liste mit Hilfe einer Knotenklasse implementiert:&lt;br /&gt;
      class Node:&lt;br /&gt;
          def __init__(self, data):&lt;br /&gt;
              self.data = data&lt;br /&gt;
              self.next = None&lt;br /&gt;
&lt;br /&gt;
Die Listenklasse selbst hat ein Feld &amp;lt;tt&amp;gt;head&amp;lt;/tt&amp;gt;, das eine Referenz auf den ersten Knoten speichert, und jeder Knoten speichert im Feld &amp;lt;tt&amp;gt;next&amp;lt;/tt&amp;gt; eine Referenz auf seinen Nachfolger. Um zum j-ten Element zu gelangen, muss man die Liste sequenziell durchlaufen&lt;br /&gt;
      def get_jth(list, j):&lt;br /&gt;
           r = list.head&lt;br /&gt;
           while j &amp;gt; 0:&lt;br /&gt;
               r = r.head&lt;br /&gt;
               j -= 1&lt;br /&gt;
           return r.data&lt;br /&gt;
Die Komplexität dieser Funktion ist offensichtlich &amp;lt;math&amp;gt;\mathcal{O}(j)&amp;lt;/math&amp;gt; (Komplexitätsberechnung wie oben). Wir setzen jetzt bei Version 1 des Mittelwert-Algorithmus diese Funktion in Zeile 6 anstelle des Indexzugriffs &amp;lt;tt&amp;gt;a[i]&amp;lt;/tt&amp;gt; ein (nur in dieser Zeile wird auf die Elemente des Arrays zugegriffen). Wir erhalten folgende Implementation (die Änderungen sind rot markiert):&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;0&amp;quot; cellpadding=&amp;quot;2&amp;quot; &lt;br /&gt;
|-&lt;br /&gt;
! Programmzeile&lt;br /&gt;
! Version 1 mit Liste: O(N * k)&lt;br /&gt;
! Komplexit&amp;amp;auml;t&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
1.&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;tt&amp;gt;r = [0] * len(a)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(N)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
2.&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;tt&amp;gt;if k &amp;gt; len(a):&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(1)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
3.&lt;br /&gt;
|&lt;br /&gt;
:: &amp;lt;tt&amp;gt;raise RuntimeError (&amp;quot;k zu gro&amp;amp;szlig;&amp;quot;)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
4.&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;tt&amp;gt;for j in range(k-1, len(a)):&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;center&amp;gt;O(N-k+1) = '''O(N)'''&amp;lt;/center&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
5.&lt;br /&gt;
|&lt;br /&gt;
:: &amp;lt;tt&amp;gt;for i in range(j-k+1, j+1):&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(k)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
6.&lt;br /&gt;
|&lt;br /&gt;
:::: &amp;lt;tt&amp;gt;r[j] += &amp;lt;font color=red&amp;gt;get_jth(a, i)&amp;lt;/font&amp;gt;&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;&amp;lt;font color=red&amp;gt;O(i)&amp;lt;/font&amp;gt;&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
7.&lt;br /&gt;
|&lt;br /&gt;
:: &amp;lt;tt&amp;gt;r[j] /= float(k)&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(1)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
8.&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;tt&amp;gt;return r&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
'''&amp;lt;center&amp;gt;O(1)&amp;lt;/center&amp;gt;'''&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Der Aufruf der Funktion &amp;lt;tt&amp;gt;get_jth&amp;lt;/tt&amp;gt; ist jetzt gleichbedeutend mit einer dreifach verschachtelten Schleife (weil &amp;lt;tt&amp;gt;get_jth&amp;lt;/tt&amp;gt; ja eine zusatzliche Schleife enthält). Die Anzahl der Operationen in Zeile 4 bis 6 ist jetzt&lt;br /&gt;
&lt;br /&gt;
::&amp;lt;math&amp;gt;f(N,k)=\sum_{j=k-1}^{N-1}\,\sum_{i=j-k+1}^j\,\mathcal{O}(i)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
wobei das &amp;lt;math&amp;gt;\mathcal{O}(i)&amp;lt;/math&amp;gt; die neue Schleife durch Verwendung der Liste repräsentiert. Mit Mathematica-Hilfe [http://www.wolfram.com/] lässt sich diese Summe exakt ausrechnen&lt;br /&gt;
&lt;br /&gt;
::&amp;lt;math&amp;gt;f(N,k)=\frac{1}{2}(k N^2-k^2 N+k^2-k)\in \mathcal{O}(k N^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Die Komplexitätsberechnung erfolgte dabei nach der Regel für Polynome unter Beachtung von &amp;lt;math&amp;gt;k \ll N&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
====Fazit:====&lt;br /&gt;
&lt;br /&gt;
Die Komplexität von Version 1 mit einer verketteten Liste wäre O(N&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; * k)&lt;br /&gt;
'''&amp;amp;rarr; Die richtige Datenstruktur ist wichtig, da es sonst zu einer Komplexitätsverschlechterung kommen kann!'''&lt;br /&gt;
&lt;br /&gt;
Auf Version 2 unseres Running Average-Beispiels hätte eine verkettete Liste allerdings keine Auswirkungen, da die inkrementelle Berechnung der Summen in Zeile 7 weiterhin möglich ist (bei geschickter Implementation!) und somit Version 2 immer noch eine Komplexität von O(N) hätte.&lt;br /&gt;
&lt;br /&gt;
==Amortisierte Komplexität==&lt;br /&gt;
&lt;br /&gt;
Bis jetzt wurde die Komplexität nur im schlechtesten Fall (Worst Case) betrachtet. Bei einigen Algorithmen schwankt die Komplexität im schlechtesten Fall jedoch, wenn man die ungünstige Operation mehrmals hintereinander ausführt. Die amortisierte Komplexität beschäftigt sich mit der durchschnittlichen Komplexität über viele Aufrufe der ungünstigsten Operation.&lt;br /&gt;
&lt;br /&gt;
Zum weiter Lesen: [[http://de.wikipedia.org/wiki/Amortisierte_Laufzeitanalyse Wikipedia: Amortisierte Laufzeitanalyse]]&lt;br /&gt;
&lt;br /&gt;
===Beispiel: Inkrementieren von Binärzahlen===&lt;br /&gt;
&lt;br /&gt;
Frage: Angenommen, das Umdrehen eines Bits einer Binärzahl verursacht Kosten von 1 Einheit. Wir erzeugen die Folge der natürlichen Zahlen durch sukzessives Inkrementieren, von Null beginnend. Bei jeder Inkrementierung werden einige Bits verändert, aber diese Zahl (und damit die Kosten der Inkrementierungen) ''schwanken'' sehr stark. Wir fragen jetzt, was eine Inkrementierung im Durchschnitt kostet?&lt;br /&gt;
&lt;br /&gt;
Um diese Durchschnittskosten zu berechnen, bezahlen wir bei jeder Inkrementierung 2 Einheiten. Wenn davon nach Abzug der Kosten der jeweiligen Operation noch etwas übrig bleibt, wird der Rest dem  Guthaben zugeschrieben. Umgekehrt wird ein eventueller Fehlbetrag (wenn eine Inkrementierung mehr als 2 Bits umdreht) aus dem Guthaben gedeckt. Dadurch werden die ansonsten großen Schwankungen der Kosten ausgeglichen:&lt;br /&gt;
:: Kosten &amp;lt; Einzahlung &amp;amp;rarr; es wird gespart&lt;br /&gt;
:: Kosten = Einzahlung &amp;amp;rarr; Guthaben bleibt unverändert&lt;br /&gt;
:: Kosten &amp;gt; Einzahlung &amp;amp;rarr; Guthaben wird für die Kosten verbraucht&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;0&amp;quot; cellpadding=&amp;quot;5&amp;quot; &lt;br /&gt;
!Schritte&lt;br /&gt;
!Zahlen&lt;br /&gt;
!Kosten &amp;lt;br/&amp;gt;&lt;br /&gt;
(Anzahl der geänderten Bits)&lt;br /&gt;
! Einzahlung&lt;br /&gt;
!Guthaben =&amp;lt;br/&amp;gt;&lt;br /&gt;
altes Guthaben + Einzahlung - Kosten&lt;br /&gt;
|-&lt;br /&gt;
|1.&lt;br /&gt;
|0000&amp;lt;u&amp;gt;&amp;lt;span style=&amp;quot;color:#00BFFF;&amp;quot;&amp;gt;1&amp;lt;/span&amp;gt;&amp;lt;/u&amp;gt;&lt;br /&gt;
|1&lt;br /&gt;
|'''2'''&lt;br /&gt;
|'''1'''&lt;br /&gt;
|-&lt;br /&gt;
|2.&lt;br /&gt;
|000&amp;lt;u&amp;gt;&amp;lt;span style=&amp;quot;color:#00BFFF;&amp;quot;&amp;gt;1&amp;lt;/span&amp;gt;&amp;lt;/u&amp;gt;&amp;lt;u&amp;gt;&amp;lt;span style=&amp;quot;color:#00BFFF;&amp;quot;&amp;gt;0&amp;lt;/span&amp;gt;&amp;lt;/u&amp;gt;&lt;br /&gt;
|2&lt;br /&gt;
|'''2'''&lt;br /&gt;
|'''1'''&lt;br /&gt;
|-&lt;br /&gt;
|3.&lt;br /&gt;
|0001&amp;lt;u&amp;gt;&amp;lt;span style=&amp;quot;color:#00BFFF;&amp;quot;&amp;gt;1&amp;lt;/span&amp;gt;&amp;lt;/u&amp;gt;&lt;br /&gt;
|1&lt;br /&gt;
|'''2'''&lt;br /&gt;
|'''2'''&lt;br /&gt;
|-&lt;br /&gt;
|4.&lt;br /&gt;
|00&amp;lt;u&amp;gt;&amp;lt;span style=&amp;quot;color:#00BFFF;&amp;quot;&amp;gt;1&amp;lt;/span&amp;gt;&amp;lt;/u&amp;gt;&amp;lt;u&amp;gt;&amp;lt;span style=&amp;quot;color:#00BFFF;&amp;quot;&amp;gt;0&amp;lt;/span&amp;gt;&amp;lt;/u&amp;gt;&amp;lt;u&amp;gt;&amp;lt;span style=&amp;quot;color:#00BFFF;&amp;quot;&amp;gt;0&amp;lt;/span&amp;gt;&amp;lt;/u&amp;gt;&lt;br /&gt;
|3&lt;br /&gt;
|'''2'''&lt;br /&gt;
|'''1'''&lt;br /&gt;
|-&lt;br /&gt;
|5.&lt;br /&gt;
|0010&amp;lt;u&amp;gt;&amp;lt;span style=&amp;quot;color:#00BFFF;&amp;quot;&amp;gt;1&amp;lt;/span&amp;gt;&amp;lt;/u&amp;gt;&lt;br /&gt;
|1&lt;br /&gt;
|'''2'''&lt;br /&gt;
|'''2'''&lt;br /&gt;
|-&lt;br /&gt;
|6.&lt;br /&gt;
|001&amp;lt;u&amp;gt;&amp;lt;span style=&amp;quot;color:#00BFFF;&amp;quot;&amp;gt;10&amp;lt;/span&amp;gt;&amp;lt;/u&amp;gt;&lt;br /&gt;
|2&lt;br /&gt;
|'''2'''&lt;br /&gt;
|'''2'''&lt;br /&gt;
|-&lt;br /&gt;
|7.&lt;br /&gt;
|0011&amp;lt;u&amp;gt;&amp;lt;span style=&amp;quot;color:#00BFFF;&amp;quot;&amp;gt;1&amp;lt;/span&amp;gt;&amp;lt;/u&amp;gt;&lt;br /&gt;
|1&lt;br /&gt;
|'''2'''&lt;br /&gt;
|'''3'''&lt;br /&gt;
|-&lt;br /&gt;
|8.&lt;br /&gt;
|0&amp;lt;u&amp;gt;&amp;lt;span style=&amp;quot;color:#00BFFF;&amp;quot;&amp;gt;1000&amp;lt;/span&amp;gt;&amp;lt;/u&amp;gt;&lt;br /&gt;
|4&lt;br /&gt;
|'''2'''&lt;br /&gt;
|'''1'''&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Die Kosten ergeben sich aus der Anzahl der Ziffern die von 1 nach 0, bzw. von 0 nach 1 verändert werden&lt;br /&gt;
&lt;br /&gt;
&amp;lt;u&amp;gt;Rechnung:&amp;lt;/u&amp;gt;&lt;br /&gt;
&lt;br /&gt;
1. Schritt: Kosten: 1 &amp;lt; Einzahlung: 2&amp;lt;br /&amp;gt;&lt;br /&gt;
:: &amp;amp;rarr; es wird gespart&amp;lt;br /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. Schritt: Kosten: 2 = Einzahlung: 2&amp;lt;br /&amp;gt;&lt;br /&gt;
:: &amp;amp;rarr; es wird nicht gespart&amp;lt;br /&amp;gt;&lt;br /&gt;
:: &amp;amp;rarr; Guthaben bleibt so wie es ist &amp;lt;br /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. Schritt: Kosten: 1 &amp;lt; Einzahlung: 2&amp;lt;br /&amp;gt;&lt;br /&gt;
:: &amp;amp;rarr; es wird gespart&amp;lt;br /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
4. Schritt: Kosten: 3 &amp;gt; Einzahlung: 2&amp;lt;br /&amp;gt;&lt;br /&gt;
:: &amp;amp;rarr; es wird eine 1 vom Guthaben genommen um die Kosten zu zahlen&amp;lt;br /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
usw.&lt;br /&gt;
&lt;br /&gt;
Man erkennt, dass vor teuren Operation (Wechsel von 3 auf 4 bzw. von 7 auf 8) genügend Guthaben angespart wurde, um die Kosten zu decken. Das Guthaben geht bei diesen Operationen immer wieder auf 1 zurück, aber es wird nie vollständig verbraucht (Dies kann natürlich auch mathematisch exakt bewiesen werden, wie wir es unten am Beispiel des dynamische Arrays zeigen). Wir schließen daraus, dass die durchschnittlichen oder '''amortisierten Kosten''' einer Inkrementierungsoperation gleich 2 sind.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Zum weiter Lesen: [[http://de.wikipedia.org/wiki/Account-Methode Wikipedia Account-Methode]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
====Fazit====&lt;br /&gt;
Die amortisierte Komplexität beschäftigt sich mit dem Durchschnitt aller Operation im ungünstigsten Fall. Operationen mit hohen Kosten, die aber nur selten ausgeführt werden, fallen bei der amortisierten Komplexität nicht so ins Gewicht. Bei Algorithmen, die gelegentlich eine &amp;quot;teure&amp;quot; Operation benutzen, ansonsten jedoch &amp;quot;billigen&amp;quot; Operationen aufrufen, kann die amortisierte Komplexität niedriger sein als die Komplexität im schlechtesten (Einzel-)Fall.&lt;br /&gt;
&lt;br /&gt;
In unserem Beispiel fallen die teuren Einzelschritte (z.B. 4. und 8. Schritt) bei den amortisierten Kosten nicht so ins Gewicht, da wir die Kosten aus unserem Guthaben mitbezahlen können. Das Guthaben ist immer groß genug, weil jeder zweite Aufruf eine billige Operation ist, die nur ein Bit umdreht und somit das Ansparen ermöglichen. Diese Betrachtung zeigt, dass die amortisierte (d.h. durchschnittliche) Komplexität des Algoithmus niedriger (nämlich konstant) ist als die Komplexität im schlechtesten Fall.&lt;br /&gt;
&lt;br /&gt;
===statisches Array===&lt;br /&gt;
&lt;br /&gt;
Ein statisches Array hat eine feste Größe N und das Erweitern des Arrays um ein neues Element besitzt eine Komplexität von O(N), &lt;br /&gt;
denn es muss ein neues Array mit der Größe N+1 erzeugt werden.&amp;lt;br /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;u&amp;gt;Anhängen eines weiteren Elements an ein statisches Array:&amp;lt;/u&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;0&amp;quot; cellpadding=&amp;quot;5&amp;quot; align=&amp;quot;right&amp;quot;&lt;br /&gt;
!Schritte&lt;br /&gt;
|'''Array'''&lt;br /&gt;
&amp;lt;small&amp;gt;(wie es aussehen könnte)&amp;lt;/small&amp;gt;&lt;br /&gt;
!Komplexität&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;center&amp;gt;altes Array&amp;lt;/center&amp;gt;&lt;br /&gt;
|&amp;lt;center&amp;gt;[0,1,2,3]&amp;lt;/center&amp;gt;&lt;br /&gt;
|&amp;lt;center&amp;gt;-&amp;lt;/center&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|1. Array N+1&lt;br /&gt;
|&amp;lt;center&amp;gt;[None,None,None,None,None]&amp;lt;/center&amp;gt;&lt;br /&gt;
|&amp;lt;center&amp;gt;O(N+1) = '''O(N)'''&amp;lt;/center&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|2. Kopieren&lt;br /&gt;
|&amp;lt;center&amp;gt;[0,1,2,3,None]&amp;lt;/center&amp;gt;&lt;br /&gt;
|&amp;lt;center&amp;gt;'''O(N)'''&amp;lt;/center&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|3. append von &amp;quot;x&amp;quot;&lt;br /&gt;
|&amp;lt;center&amp;gt;[0,1,2,3,'x']&amp;lt;/center&amp;gt;&lt;br /&gt;
|&amp;lt;center&amp;gt;'''O(1)'''&amp;lt;/center&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
altesArray = [0,1,2,3]&amp;lt;br /&amp;gt;&lt;br /&gt;
altesArray.append('x')&lt;br /&gt;
&lt;br /&gt;
1. Es wird ein neues Array der Größe N+1 erzeugt&amp;lt;br /&amp;gt;&lt;br /&gt;
2. Die Daten aus dem alten Array werden in das neue Array mit der Länge N+1 kopiert&amp;lt;small&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
(Die Operation besitzt nur eine Komplexität von O(N), wenn  das Kopieren eines Elements eine Komplexität von O(1) besitzt)&amp;lt;/small&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
3. 'x' wird an die letzte Stelle des neuen Arrays geschrieben&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;u&amp;gt;Additionsregel:&amp;lt;/u&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
O(N) + O(N) + O(1) &amp;amp;isin; O(N), falls O(1) &amp;amp;isin; O(N) [O(max(O(N),O(1))] &amp;lt;small&amp;gt;(Bedingung: N &amp;gt; 1)&amp;lt;/small&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===dynamisches Array===&lt;br /&gt;
&lt;br /&gt;
Beim dynamischen Array werden mehr Speicherelemente reserviert als zur Zeit benötigt. Wir unterscheiden deshalb &lt;br /&gt;
&lt;br /&gt;
::&amp;lt;tt&amp;gt;capacity&amp;lt;/tt&amp;gt; = Anzahl der möglichen Elemente, die in das Array passen&amp;lt;br /&amp;gt;&lt;br /&gt;
::&amp;lt;tt&amp;gt;size&amp;lt;/tt&amp;gt; = Anzahl der Elemente, die im Array zur Zeit gespeichert sind&amp;lt;br /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Die Daten selbst werden in einem statischen Array gespeichert:&lt;br /&gt;
::&amp;lt;tt&amp;gt;data&amp;lt;/tt&amp;gt; = statisches Array der Größe &amp;quot;capacity&amp;quot;&amp;lt;br /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ist dieses Array voll, wird ein neues Datenarray der doppelten Größe allokiert, und die Daten werden vom alten ins neue Array kopiert. Die Vorgehensweise &lt;br /&gt;
beim Zufügen eines neuen Elements im Fall (size == capacity) ist also&lt;br /&gt;
* capacity wird verdoppelt&amp;lt;br /&amp;gt;&lt;br /&gt;
: neue capacity = 2 * alte capacity&lt;br /&gt;
: (allgemein genügt es auch, wenn capacity wird um einen bestimmten Prozentsatz vergrößert wird, &lt;br /&gt;
:: neue capacity = alte capacity * c (mit c &amp;gt; 1, z.B. c = 1.2)&lt;br /&gt;
* ein neues statisches Array der Größe 'neue capacity' wird erzeugt&lt;br /&gt;
* das alte Array wird ins neue kopiert und danach freigegeben&lt;br /&gt;
* das anzufügende Element wird ins neue Array eingefügt&lt;br /&gt;
&lt;br /&gt;
'''Folge:''' Die Kosten für das Vergrößern der Kapazität werden amortisiert über viele Einfügungen, die kein Vergrößern erfordern. Die Operation &amp;lt;tt&amp;gt;append&amp;lt;/tt&amp;gt; besitzt amortisierte Komplexität  O(1), wie wir jetzt zeigen:&lt;br /&gt;
&lt;br /&gt;
===Analyse des dynamischen Arrays===&lt;br /&gt;
&lt;br /&gt;
Durchschnitt der Gesamtkosten für N-maliges append = &amp;lt;math&amp;gt;\frac{1}{N} \sum_{i = 1}^N Kosten(i)&amp;lt;/math&amp;gt;. Zur Analyse der amortisierten Komplexität wird ein Potential&amp;lt;br/&amp;gt;&lt;br /&gt;
::&amp;amp;Phi;&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; = 2*size&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; - capacity&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
eingeführt, wobei das Array nach dem i-ten Einfüge-Schritt die Größe size&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; und die Kapizität capacity&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; hat. Wir nehmen vereinfachend an, dass es keine Löschoperationen gibt. Dann gilt nach dem i-ten Schritt jeweils&lt;br /&gt;
::&amp;amp;Phi;&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; = 2*i - capacity&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;u&amp;gt;Fall 1: Array ist nicht voll&amp;lt;/u&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
Es wird kein Umkopieren benötigt, da das Array noch nicht voll ist&amp;lt;br/&amp;gt; &lt;br /&gt;
&amp;amp;rarr; size&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt; &amp;lt; capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt;&amp;lt;br/&amp;gt; &lt;br /&gt;
&amp;amp;rarr; capacity&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; == capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Kosten: 1 (für Einfügen des neuen Elements)&amp;lt;br /&amp;gt;&lt;br /&gt;
Potenzial vor append: &amp;amp;Phi;&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt; = 2(i - 1) - capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
Potenzial nach append: &amp;amp;Phi;&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; = 2i - capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
amortisierte Kosten = Kosten&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; + &amp;amp;Phi;&amp;lt;sub&amp;gt;(i)&amp;lt;/sub&amp;gt; - &amp;amp;Phi;&amp;lt;sub&amp;gt;(i-1)&amp;lt;/sub&amp;gt;&lt;br /&gt;
:::::                 = 1            + (2i - capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt;)        - [2(i - 1) - capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt;]&lt;br /&gt;
:::::                 = 1            + 2i - capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt;         - 2i + 2 + capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt;&lt;br /&gt;
:::::                 = 1            + &amp;lt;del&amp;gt;2i&amp;lt;/del&amp;gt; - &amp;lt;del&amp;gt;capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt;&amp;lt;/del&amp;gt; - &amp;lt;del&amp;gt;2i&amp;lt;/del&amp;gt; + 2 + &amp;lt;del&amp;gt;capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt;&amp;lt;/del&amp;gt;&lt;br /&gt;
:::::                 = 1 + 2&lt;br /&gt;
:::::                 = 3 = O(1) &amp;amp;rarr; konstant&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;u&amp;gt;Fall 2: Array ist voll&amp;lt;/u&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
Vor dem i-ten append muss umkopiert werden&amp;lt;br/&amp;gt; &lt;br /&gt;
&amp;amp;rarr; size&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt; == capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt; == i-1&amp;lt;br/&amp;gt; &lt;br /&gt;
&amp;amp;rarr; Allokieren eines neuen statischen Arrays mit verdoppelter Kapazität notwendig, also capacity&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; == 2*capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Kosten: (i-1) + 1 (für Umkopieren und Einfügen des neuen Elements)&amp;lt;br /&amp;gt;&lt;br /&gt;
Potenzial vor append =  &amp;amp;Phi;&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt; = 2(i - 1) - capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
Potenzial nach append =  &amp;amp;Phi;&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; =  2i - 2 capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
amortisierte Kosten = Kosten&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; + &amp;amp;Phi;&amp;lt;sub&amp;gt;(i)&amp;lt;/sub&amp;gt; - &amp;amp;Phi;&amp;lt;sub&amp;gt;(i-1)&amp;lt;/sub&amp;gt;&lt;br /&gt;
:::::               = ((i - 1) + 1) + 2i - 2 capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt; - [2(i-1) - capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt;]&lt;br /&gt;
:::::               = i + &amp;lt;del&amp;gt;2i&amp;lt;/del&amp;gt; - 2 capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt; - &amp;lt;del&amp;gt;2i&amp;lt;/del&amp;gt; + 2 - capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt;&lt;br /&gt;
:::::               = i + 2 - capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt;&lt;br /&gt;
:::::               = i + 2 - (i - 1) &amp;lt;small&amp;gt;(da capacity&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt; = i-1)&amp;lt;/small&amp;gt;&lt;br /&gt;
:::::               = 3 = O(1) &amp;amp;rarr; konstant            &lt;br /&gt;
&lt;br /&gt;
'''Damit wurde bewiesen, dass die Operation &amp;lt;tt&amp;gt;append&amp;lt;/tt&amp;gt; beim dynamischen Array eine amortisierte Komplexität von 3 Einheiten hat, also &amp;lt;tt&amp;gt;append&amp;lt;/tt&amp;gt; &amp;amp;isin; O(1)'''. Diese Operation kann deshalb gefahrlos in der inneren Schleife eines Algorithmus benutzt werden.&lt;br /&gt;
&lt;br /&gt;
==== Beispiel für 9 Einfügeoperationen ====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;0&amp;quot; cellpadding=&amp;quot;5&amp;quot;&lt;br /&gt;
!Array&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;small&amp;gt;(wie es aussehen könnte)&amp;lt;/small&amp;gt;&lt;br /&gt;
!size&lt;br /&gt;
!capacity&lt;br /&gt;
!Kosten für append&amp;lt;br /&amp;gt;(einschließlich Umkopieren)&lt;br /&gt;
!Summe Kosten&lt;br /&gt;
!Durchschnittskosten&lt;br /&gt;
!&amp;amp;Phi;&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; = 2 * size - capacity&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;small&amp;gt;(i = size)&amp;lt;/small&amp;gt;&lt;br /&gt;
!Potenzialdifferenz&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;amp;Delta; &amp;amp;Phi;&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; = &amp;amp;Phi;&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; - &amp;amp;Phi;&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt;&lt;br /&gt;
!amortisierte Kosten&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
= Kosten&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; + &amp;amp;Delta; &amp;amp;Phi;&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;center&amp;gt;[None]&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;0&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;1&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;-&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;-&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;-&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;-1&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;-&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;-&amp;lt;/center&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;center&amp;gt;[a]&amp;lt;/center&amp;gt;&amp;lt;center&amp;gt;&amp;lt;span style=&amp;quot;color:#00BFFF;&amp;quot;&amp;gt;Array ist voll!&amp;lt;/span&amp;gt;&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;1&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;1&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;1&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;1&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;1&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;1&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;2&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;3&amp;lt;/center&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;center&amp;gt;[a,b]&amp;lt;/center&amp;gt;&amp;lt;center&amp;gt;&amp;lt;span style=&amp;quot;color:#00BFFF;&amp;quot;&amp;gt;Array ist voll!&amp;lt;/span&amp;gt;&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;2&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;2&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;1 + 1&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;3&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;3/2&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;2&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;1&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;3&amp;lt;/center&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;center&amp;gt;[a,b,c,None]&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;3&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;4&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;2 + 1&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;6&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;6/3&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;2&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;0&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;3&amp;lt;/center&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;center&amp;gt;[a,b,c,d]&amp;lt;/center&amp;gt;&amp;lt;center&amp;gt;&amp;lt;span style=&amp;quot;color:#00BFFF;&amp;quot;&amp;gt;Array ist voll!&amp;lt;/span&amp;gt;&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;4&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;4&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;1&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;7&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;7/4&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;4&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;2&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;3&amp;lt;/center&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;center&amp;gt;[a,b,c,d,e,None,None,None]&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;5&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;8&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;4 + 1&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;12&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;12/5&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;2&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;-2&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;3&amp;lt;/center&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;center&amp;gt;[a,b,c,d,e,f,None,None]&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;6&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;8&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;1&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;13&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;13/6&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;4&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;2&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;3&amp;lt;/center&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;center&amp;gt;[a,b,c,d,e,f,g,None]&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;7&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;8&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;1&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;14&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;14/7&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;6&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;2&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;3&amp;lt;/center&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;center&amp;gt;[a,b,c,d,e,f,g,h]&amp;lt;/center&amp;gt;&amp;lt;center&amp;gt;&amp;lt;span style=&amp;quot;color:#00BFFF;&amp;quot;&amp;gt;Array ist voll!&amp;lt;/span&amp;gt;&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;8&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;8&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;1&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;15&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;15/8&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;8&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;2&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;3&amp;lt;/center&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;center&amp;gt;[a,b,c,d,e,f,g,h,j,None,None,None,&amp;lt;br /&amp;gt;&lt;br /&gt;
None,None,None,None]&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;9&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;16&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;8 + 1&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;24&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;24/9&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;2&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;-6&amp;lt;/center&amp;gt;&lt;br /&gt;
| &amp;lt;center&amp;gt;3&amp;lt;/center&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Die durchschnittlichen Kosten betragen stets etwa 2 Einheiten, schwanken allerdings so, dass nicht unmittelbar ersichtlich ist, ob dies für sämtliche Einfügeoperationen gilt. Die amortisierte Komplexität, die mit Hilfe des Potentials berechnet wird, ist hingegen konstant 3, wie auch im obigen Beweis für alle Einfügeoperationen allgemein gezeigt wurde.&lt;/div&gt;</summary>
		<author><name>Jschleic</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Korrektheit&amp;diff=2525</id>
		<title>Korrektheit</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Korrektheit&amp;diff=2525"/>
				<updated>2008-07-22T09:02:00Z</updated>
		
		<summary type="html">&lt;p&gt;Jschleic: typo&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Man unterscheidet zwischen Prüfung der Korrektheit (Verifikation) und Prüfung der Spezifikation (Validierung). Ein Algorithmus heißt korrekt, wenn er sich gemäß seiner Spezifikation verhält, auch wenn seine Spezifikation nicht immer die gewünschten Ergebnisse liefert. Die Spezifikation beschreibt die Vorbedingungen (was vor der Anwendung des Algorithmus gilt, so dass der Algorithmus überhaupt angewendet werden darf) und die Nachbedingungen (was nach der Anwendung des Algorithmus gilt, welchen Zustand des Systems der Algorithmus also erzeugt). Hier geht es ausschliesslich um die Prüfung der Korrektheit eines Algorithmus, also darum, ob die spezifizierten Nachbedingungen wirklich gelten.&lt;br /&gt;
 &lt;br /&gt;
Nebenbemerkungen&lt;br /&gt;
# es gibt Algorithmen, die ''nie'' mit einer 100-prozentigen Wahrscheinlichkeit richtige Ergebnisse liefern können (z.B. [http://en.wikipedia.org/wiki/Primality_test#Probabilistic_tests nichtdeterministische Primzahltests]). &lt;br /&gt;
# '''Korrektheit''' wird in Algorithmenbüchern meist nur im Zusammenhang mit konkreten Algorithmen behandelt, aber nicht als übergreifendes Problem. Dies erscheint der Bedeutung von Korrektheit nicht angemessen.&lt;br /&gt;
&lt;br /&gt;
Will man die Korrektheit eines Algorithmus/Programms feststellen, hat man 3 Vorgehensweisen zur Verfügung: Prüfung der syntaktischen Korrektheit, formaler Korrektheitsbeweis und Softwaretest.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Syntaktische Korrektheit ==&lt;br /&gt;
&lt;br /&gt;
Die syntaktische Korrektheit behandeln wir hier nur kurz und der Vollständigkeit halber. Sie wird in den Veranstaltungen zur theoretischen Informatik (Grammatiken) und zum Compilerbau ausführlich behandelt.&lt;br /&gt;
&lt;br /&gt;
=== Syntaktische Prüfung ===&lt;br /&gt;
Es wird eine Grammatik definiert, deren Regeln die Implementation des Algorithmus befolgen muss. Für ein Programm heißt das beispielsweise, dass die Syntax der Programmiersprache eingehalten werden muss.&lt;br /&gt;
&lt;br /&gt;
Vorteile des Verfahrens: die Richtigkeit der Syntax lässt sich leicht vom Compiler/Interpreter überprüfen (mehr dazu in der Theoretischen Informatik und Compilerbau). Somit ist es die einfachste Möglichkeit, viele inkorrekte Programme schnell zu erkennen und zurückzuweisen.&lt;br /&gt;
  &amp;gt;&amp;gt;&amp;gt; if a==0&lt;br /&gt;
    File &amp;quot;&amp;lt;stdin&amp;gt;&amp;quot;, line 1&lt;br /&gt;
      if a==0&lt;br /&gt;
            ^&lt;br /&gt;
  SyntaxError: invalid syntax&lt;br /&gt;
&lt;br /&gt;
=== Typprüfung ===&lt;br /&gt;
Ein Typ definiert Gruppierung der Daten und die Operationen, die für diese Datengruppierung erlaubt sind(konkreter Typ) bzw. die Bedeutung der Daten und die erlaubten Operationen (abstrakter Datentyp, vgl. Dreieck aus der [[Einführung#Definition von Datenstrukturen|ersten Vorlesung]]). Typen sind Zusicherungen an den Algorithmus und den Compiler/Interpreter, dass Daten und deren Operationen bestimmte semantische Bedingungen einhalten. Wenn man innerhalb des Algorithmus mit Typen arbeitet, darf man von der semantischen Korrektheit der erlaubten Operationen ausgehen. Umgekehrt können Operationen, die zu Typkonflikten führen würden, leicht als inkorrekt zurückgeweisen werden.&lt;br /&gt;
&lt;br /&gt;
Vorteile des Verfahrens: Typprüfung ist teuerer als syntaktische Prüfung, aber billiger als andere Prüfungen der Korrektheit (mehr dazu im Kapitel [[Generizität]]).&lt;br /&gt;
  &amp;gt;&amp;gt;&amp;gt; a=3&lt;br /&gt;
  &amp;gt;&amp;gt;&amp;gt; b=None&lt;br /&gt;
  &amp;gt;&amp;gt;&amp;gt; a+b&lt;br /&gt;
  Traceback (most recent call last):&lt;br /&gt;
    File &amp;quot;&amp;lt;stdin&amp;gt;&amp;quot;, line 1, in &amp;lt;module&amp;gt;&lt;br /&gt;
  TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'&lt;br /&gt;
&lt;br /&gt;
In python ist (ebenso wie in vielen anderen Programmiersprachen) explizite Typprüfung möglich:&lt;br /&gt;
  &amp;gt;&amp;gt;&amp;gt; import types&lt;br /&gt;
  &amp;gt;&amp;gt;&amp;gt; a=3&lt;br /&gt;
  &amp;gt;&amp;gt;&amp;gt; b=None&lt;br /&gt;
  &amp;gt;&amp;gt;&amp;gt; if isinstance(b, types.IntType): # prüft, ob b ein Integer ist&lt;br /&gt;
  ...     print a+b&lt;br /&gt;
  ... else:&lt;br /&gt;
  ...     raise TypeError, &amp;quot;b ist kein Integer&amp;quot; # falls b kein Integer ist, wird ein TypeError ausgelöst&lt;br /&gt;
  ... &lt;br /&gt;
 &lt;br /&gt;
  Traceback (most recent call last):&lt;br /&gt;
    File &amp;quot;&amp;lt;stdin&amp;gt;&amp;quot;, line 4, in &amp;lt;module&amp;gt;&lt;br /&gt;
  TypeError: b ist kein Integer&lt;br /&gt;
&lt;br /&gt;
== Formaler Korrektheitsbeweis ==&lt;br /&gt;
=== (Halb-)Automatisches Beweisen ===&lt;br /&gt;
Man versucht, die Hypothese H: ''Algorithmus ist korrekt'' entweder mathematisch zu beweisen oder zu widerlegen. Dieses Beweisverfahren heißt dann halbautomatisch, wenn der Mensch in den Entscheidungsprozess miteinbezogen wird.&lt;br /&gt;
&lt;br /&gt;
Um den Beweis durchführen zu können, ist folgendes nötig:&lt;br /&gt;
;eine [http://en.wikipedia.org/wiki/Formal_specification formale Spezifikation] des Algorithmus: eine formale Spezifikation wird in einer [http://en.wikipedia.org/wiki/Specification_language Spezifikationssprache] geschrieben (z.B. [http://en.wikipedia.org/wiki/Z_notation Z]). Sie ist &lt;br /&gt;
:* deklarativ (d.h. beschreibt, was das Programm tun soll, ist selbst aber nicht ausführbar)&lt;br /&gt;
:* formal präzise (kann nur auf eine einzige Weise interpretiert werden)&lt;br /&gt;
:* hierarchisch aufgebaut (eine Spezifikation für einen komplizierten Algorithmus greift auf Spezifikationen für einfache Bestandteile dieses Algorithmus zurück)&lt;br /&gt;
:* so einfach, dass ihre Korrektheit für einen Menschen mit entsprechender Erfahrung unmittelbar einsichtig ist (denn eine Spezifikation kann nicht formal bewiesen werden - dafür wäre eine weitere Spezifikation nötig, die auch bewiesen werden müsste usw.)&lt;br /&gt;
;ein axiomatisiertes Programmiermodell: zum Beispiel&lt;br /&gt;
:* eine axiomatisierbare Programmiersprache, wie z.B. WHILE-Programm (s. [[Einführung#Zur Frage der elementaren Schritte|erste Vorlesung]]), Pascal (siehe dazu Hoare's [http://delivery.acm.org/10.1145/70000/63445/cb-p153-hoare.pdf?key1=63445&amp;amp;key2=5041959021&amp;amp;coll=ACM&amp;amp;dl=ACM&amp;amp;CFID=15151515&amp;amp;CFTOKEN=6184618 grundlegenden Artikel]) und rein funktionale Programmiersprachen&lt;br /&gt;
:* ein axiomatisierbares Subset einer Programmiersprache (die meisten Programmiersprachen sind zu komplex, um als Ganzes axiomatisierbar zu sein)&lt;br /&gt;
:* endliche Automaten&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Der Korrektheitsbeweis kann beispielsweise mit dem Hoare-Kalkül (Hoare-Logik) durchgeführt werden (Hoare erfand u.a. den Quicksort-Algorithmus). Diese Methode wurde in &lt;br /&gt;
:  C.A.R. Hoare: ''&amp;quot;An Axiomatic Basis for Computer Programming&amp;quot;'', Communications of the ACM, 1969 [http://www.cs.ucsb.edu/~kemm/courses/cs266/hoare69.pdf] &lt;br /&gt;
erstmalig beschrieben. Im folgenden wird das Verfahren an einem Beispiel erläutert.&lt;br /&gt;
&lt;br /&gt;
==== Beispiel-Algorithmus ====&lt;br /&gt;
Zuerst brauchen wir einen Algorithmus, den wir auf Korrektheit prüfen wollen. Wir nehmen als Beispiel die Division x/y durch sukzessives Subtrahieren.&lt;br /&gt;
&lt;br /&gt;
 Vorbedingungen:&lt;br /&gt;
    int x,y&lt;br /&gt;
   0 &amp;lt; y &amp;lt;= x&lt;br /&gt;
 Gesucht:&lt;br /&gt;
    Quotient q, Rest r&lt;br /&gt;
 Algorithmus:&lt;br /&gt;
    r = x&lt;br /&gt;
    q = 0&lt;br /&gt;
    while y &amp;lt;= r:&lt;br /&gt;
        r = r - y&lt;br /&gt;
        q = q + 1&lt;br /&gt;
 Nachbedingungen:&lt;br /&gt;
    x == r + y*q and r &amp;lt; y&lt;br /&gt;
&lt;br /&gt;
==== Aufbau der Hoare-Logik ====&lt;br /&gt;
&lt;br /&gt;
Grundlegende syntaktische Struktur:&lt;br /&gt;
: p {Q} r&lt;br /&gt;
mit '''p''':Vorbedingung, '''Q''': Operation, '''r''': Nachbedingung.&lt;br /&gt;
Es bedeutet also schlicht: wenn man im Zustand '''p''' ist und eine Operation '''Q''' ausführt, kommt man in den Zustand '''r'''. Hat eine Operation keine Vorbedingung, schreibt man &lt;br /&gt;
: true {Q} r&lt;br /&gt;
&lt;br /&gt;
Die Hoare-Logik besteht aus 5 Axiomen:&lt;br /&gt;
;D0 - Axiom der Zuweisung: (Rule of Assignment)&lt;br /&gt;
:: R[t] {x=t} R[x]&lt;br /&gt;
  &lt;br /&gt;
: '''Beispiel:''' t==5 {x=t} x==5&lt;br /&gt;
&lt;br /&gt;
:Vorbedingung und Nachbedingung sind gleich, mit Ausnahme der Variablen x und t, die in der Zuweisung verknüpft werden: Man erhält die Vorbedingung, wenn man in der Nachbedingung alle Vorkommen von x (bzw. allgemein: alle Vorkommen der linken Variable der Zuweisung) durch t (bzw. allgemein: durch die rechte Variable der Zuweisung) ersetzt.&lt;br /&gt;
&lt;br /&gt;
;D1 - Konsequenzregeln: (Rules of Consequence, besteht aus zwei Axiomen)&lt;br /&gt;
:'''D1(a):''' wenn gilt&lt;br /&gt;
:: P {Q} R und R &amp;amp;rArr; S&lt;br /&gt;
:dann gilt auch&lt;br /&gt;
:: P {Q} S&lt;br /&gt;
:'''D1(b):''' wenn gilt &lt;br /&gt;
:: P {Q} R und S &amp;amp;rArr; P&lt;br /&gt;
:dann gilt auch&lt;br /&gt;
:: S {Q} R&lt;br /&gt;
:'''Beispiel:''' Für jede ganze Zahl gilt (x&amp;gt;5) &amp;amp;rArr; (x&amp;gt;0). Gilt außerdem (x&amp;gt;5) dann gilt erst recht (x&amp;gt;0).&lt;br /&gt;
&lt;br /&gt;
;D2 - Sequenzregel: (Rule of Composition)&lt;br /&gt;
:wenn gilt&lt;br /&gt;
:: P {Q&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;} R&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; und R&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; {Q&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;} R &lt;br /&gt;
:dann gilt auch&lt;br /&gt;
:: P {Q&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;, Q&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;} R&lt;br /&gt;
:Das heißt: wenn man P hat und Q&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; darauf anwendet, kommt man zu R&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;. Wenn man R&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; hat und Q&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; darauf anwendet, kommt man zu R. Deshalb kann man das so verkürzen: wenn man P hat und nacheinander Q&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; und Q&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; darauf anwendet, kommt man zu R.&lt;br /&gt;
&lt;br /&gt;
;D3 - Iterationsregel: (Rule of Iteration)&lt;br /&gt;
:wenn gilt&lt;br /&gt;
:: (P &amp;amp;and; B) {S} P&lt;br /&gt;
:dann gilt auch&lt;br /&gt;
:: P { while B do S } (&amp;amp;not;B &amp;amp;and; P)&lt;br /&gt;
:P wird dabei als '''Schleifeninvariante''' bezeichnet, weil es sowohl in der Vor- als auch in der Nachbedingung gilt. B ist die '''Schleifenbedingung''' - solange B erfüllt ist, wird die Schleife weiter ausgeführt.&lt;br /&gt;
&lt;br /&gt;
Da wir in dem Divisions-Algorithmus mit dem Typ '''int''' arbeiten, brauchen wir außerdem die für diesen Typ erlaubten Operationen, also die Axiome der ganzen Zahlen.&lt;br /&gt;
: '''A1:''' Kommutativität  x+y=y+x, x*y=y*x&lt;br /&gt;
: '''A2:''' Assoziativität  (x+y)+z=x+(y+z), (x*y)*z=x*(y*z)&lt;br /&gt;
: '''A3:''' Distributivität  x*(y+z)=x*y+x*z&lt;br /&gt;
: '''A4:''' Subtraktion (Inverses Element)  y&amp;amp;le;x &amp;amp;rArr; (x-y)+y=x&lt;br /&gt;
: '''A5:''' Neutrale Elemente  x+0=x, x*0=0, x*1=x&lt;br /&gt;
&lt;br /&gt;
==== Beweisen des Algorithmus ====&lt;br /&gt;
Vorbedingung: 0 &amp;lt; y,x&lt;br /&gt;
&lt;br /&gt;
Schleifeninvariante P (gleichzeitig Nachbedingung): x == y*q + r&lt;br /&gt;
  (1)  true &amp;amp;rArr; x==x+y*0                                          y*0==0 und x==x+0 folgen aus A5&lt;br /&gt;
  (2)  x==x+y*0              {r=x}                  x==r+y*0     D0: ersetze x durch r&lt;br /&gt;
  (3)  x==r+y*0              {q=0}                  x==r+y*q     D0: ersetze 0 durch q&lt;br /&gt;
  (4)  true                  {r=x}                  x==r+y*0     D1(b): kombiniere (1) und (2)&lt;br /&gt;
  (5)  true                  {r=x, q=0}             x==r+y*q     D2: kombiniere (4) und (3)&lt;br /&gt;
  (6)  x==r+y*q &amp;amp;and; y=r &amp;amp;rArr; x==(r-y)+y*(1+q)                       folgt aus A1...A5&lt;br /&gt;
  (7)  x==(r-y)+y*(1+q)      {r=r-y}                x==r+y*(1+q) D0: ersetze (r-y) durch r&lt;br /&gt;
  (8)  x==r+y*(1+q)          {q=q+1}                x==r+y*q     D0: ersetze (q+1) durch q&lt;br /&gt;
  (9)  x==(r-y)+y*(1+q)      {r=r-y, q=q+1}         x==r+y*q     D2: kombiniere (7) und (8)&lt;br /&gt;
  (10) x==r+y*q &amp;amp;and; y&amp;amp;le;r        {r=r-y, q=q+1}         x==r+y*q     D1(b): kombiniere (6) und (9)&lt;br /&gt;
  (11) x==r+y*q    {while y&amp;amp;le;r do (r=r-y, q=q+1)} x==r+y*q &amp;amp;and; &amp;amp;not;(y&amp;amp;le;r) D3: transformiere (10)&lt;br /&gt;
  (12) true        {r=x, q=0, &lt;br /&gt;
                    while y&amp;amp;le;r do (r=r-y, q=q+1)} x==r+y*q &amp;amp;and; &amp;amp;not;(y&amp;amp;le;r) D2: kombiniere (5) und (11)&lt;br /&gt;
&lt;br /&gt;
Im obigen Beweis ergibt sich sogar ''true'' als Vorbedingung (i.e. es gibt keine Vorbedingung). Dies liegt daran, dass Hoare in seinem Artikel durchweg von nicht-negativen Zahlen ausgeht. Diese Annahme wird beim Beweis von Zeile (6) benutzt.&lt;br /&gt;
&lt;br /&gt;
In der Praxis führt man solche Beweise natürlich nicht von Hand, sondern benutzt geeignete Programme, sogenannte [http://en.wikipedia.org/wiki/Automated_theorem_proving automatische Beweiser], die man allerding oft interaktiv steuern muss, weil der Beweis ohne diese Hilfe zu lange dauern würde.&lt;br /&gt;
&lt;br /&gt;
=== (Halb-)Automatisches Verfeinern ===&lt;br /&gt;
Dieses Verfahren ist beliebter, als das (halb-)automatische Beweisen. Die formale Spezifikation wird nach bestimmten, semantik-erhaltenden Transformationsregeln in ein ausführbares Programm umgewandelt. Mehr dazu z.B. in der [http://en.wikipedia.org/wiki/Program_refinement Wikipedia (Program refinement)]. Der Vorteil dieser Methode besteht darin, dass man die Transformationsregeln so definieren kann, dass nur das axiomatisierte Subset der Zielsprache benutzt wird. Dadurch wird der Korrektheitsbeweis stark vereinfacht.&lt;br /&gt;
&lt;br /&gt;
==Software-Tests==&lt;br /&gt;
&lt;br /&gt;
Dijkstra [http://de.wikipedia.org/wiki/Edsger_Wybe_Dijkstra] ließ einmal den Satz verlauten: &amp;quot;Tests können nie die Abwesenheit von Fehlern beweisen [Anwesenheit schon]&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Nach solch einer Aussage stellt sich die Frage, ob es sich überhaupt lohnt, mit dem Testverfahren die Korrektheit eines Algorithmus zu zeigen. Es erscheint einem doch plausibler sich auf die &amp;quot;formalen Methoden&amp;quot; zu berufen, mit dem Wissen, dass diese uns tatsächlich einen Beweis liefern können, ob nun H oder nicht H gilt. Zudem kommt noch erschwerend hinzu, dass es bei Tests bisher keine Theorie gibt, die sicherstellt, dass das Testprogramm einen vorhandenen Fehler zumindest mit hoher Wahrscheinlichkeit findet.&lt;br /&gt;
&lt;br /&gt;
Ein [http://de.wikipedia.org/wiki/Softwaretest Software-Test] versucht, ein Gegenbeispiel zur Hypothese H &amp;quot;der Algorithmus ist korrekt&amp;quot; zu finden. Dabei gibt es 4 Möglichkeiten:&lt;br /&gt;
 &lt;br /&gt;
   Algorithmus	   Testantwort	&lt;br /&gt;
      +	               +	        Algorithmus ist richtig, kein Gegenbeispiel gefunden&lt;br /&gt;
      -	               -	        Alg. ist falsch, und der Test erkennt den Fehler&lt;br /&gt;
      +	               -	        Bug im Test (Gegenbeispiel, obwohl Alg. richtig ist)&lt;br /&gt;
      -	               +	        Test hat versagt, da er den Fehler im Alg. nicht erkannt hat&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Wenn ein Gegenbeispiel zu H gefunden wird, kann man den Algorithmus (oder den Test) debuggen. Wird hingegen keines gefunden, nimmt man an, dass der Algorithmus korrekt ist. Man sieht, dass diese Annahme im Fall 4 nicht stimmt. Da Softwaretests jedoch in der Praxis sehr erfolgreich verwendet werden, ist dieser Fall offenbar nicht so häufig, dass man das Testen als Methode generell ablehnen müßte.&lt;br /&gt;
&lt;br /&gt;
=== Beispiel für das Testen: Freivalds Algorithmus ===&lt;br /&gt;
&lt;br /&gt;
Wir wollen die Wahrscheinlichkeit, dass ein Test einen vorhandenen Fehler übersieht, am Beispiel des [http://en.wikipedia.org/wiki/Freivald's_algorithm Algorithmus von Freivald] studieren. Es handelt sich dabei um einen randomisierten Algorithmus zum Testen der Matrixmultiplikation (siehe J. Hromkovič: ''&amp;quot;Randomisierte Algorithmen&amp;quot;'', Teubner 2004). Ziel dieses Algorithmuses ist es, die Hypothese H: &amp;quot;C ist das Produkt der Matrizen A und B&amp;quot; durch ein Gegenbeispiel zu widerlegen, wobei der Test einen anderen Algorithmus verwendet, um Vergleichsdaten zu gewinnen.&lt;br /&gt;
&lt;br /&gt;
  gegeben:&lt;br /&gt;
       Matrizen A, B, C  der Größe NxN &lt;br /&gt;
       Testhypothese H:  &amp;lt;tt&amp;gt;A*B == C&amp;lt;/tt&amp;gt;  Matrixmultiplikation (d.h. C wurde vorher durch C = mmul(A, B) berechnet, &lt;br /&gt;
                                            wobei mmul() der zu testende Multiplikationsalgorithmus ist).&lt;br /&gt;
 &lt;br /&gt;
  (1) Initialisierung      &lt;br /&gt;
       wähle Zufallsvektor der Länge N aus Nullen und Einsen: &amp;lt;math&amp;gt;\alpha \in \{0, 1\}^N &amp;lt;/math&amp;gt;  &lt;br /&gt;
  (2) Matrix-Vektor-Multiplikation (keine Matrix-Matrix-Multiplikation, denn die soll ja gerade verifiziert werden)&lt;br /&gt;
 &lt;br /&gt;
       &amp;lt;math&amp;gt;\left.\begin{array}{l}&lt;br /&gt;
                \beta = B*\alpha \\&lt;br /&gt;
                \gamma=A*\beta&lt;br /&gt;
                \end{array}\right\}A*(B*\alpha) == (A*B)*\alpha&lt;br /&gt;
       &amp;lt;/math&amp;gt; &lt;br /&gt;
 &lt;br /&gt;
       &amp;lt;math&amp;gt;\delta=C*\alpha&amp;lt;/math&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
  (3) Test der Korrektheit: falls &amp;lt;tt&amp;gt;A*B == C&amp;lt;/tt&amp;gt;, liefert der folgende Test stets &amp;lt;tt&amp;gt;true&amp;lt;/tt&amp;gt;:&lt;br /&gt;
 &lt;br /&gt;
       return   γ==δ&lt;br /&gt;
&lt;br /&gt;
Wir analysieren nun, mit welcher Wahrscheinlichkeit der Algorithmus den Fehler findet, wenn es denn einen gibt, d.h.&lt;br /&gt;
   &lt;br /&gt;
*Wahrscheinlichkeit '''p''', dass Freivalds Algorithmus den Fehler findet&amp;lt;br/&amp;gt;&lt;br /&gt;
oder&amp;lt;br/&amp;gt;&lt;br /&gt;
*Wahrscheinlichkeit '''q = 1 - p''', dass Freivalds Algorithmus den Fehler '''nicht''' findet.&lt;br /&gt;
&lt;br /&gt;
Wir schätzen diese Wahrscheinlichkeit ab für den einfachen Fall N=2. Wir definieren:&lt;br /&gt;
    &lt;br /&gt;
   &amp;lt;math&amp;gt;C=&lt;br /&gt;
  \begin{pmatrix} &lt;br /&gt;
    c_{11} &amp;amp; c_{12}  \\ &lt;br /&gt;
    c_{21} &amp;amp; c_{22}  &lt;br /&gt;
  \end{pmatrix},\qquad&lt;br /&gt;
\alpha=\begin{pmatrix}&lt;br /&gt;
    \alpha_1 \\&lt;br /&gt;
    \alpha_2 &lt;br /&gt;
     \end{pmatrix},\qquad&lt;br /&gt;
 \delta=\begin{pmatrix}&lt;br /&gt;
    \delta_1 \\&lt;br /&gt;
    \delta_2&lt;br /&gt;
 \end{pmatrix}&lt;br /&gt;
  = \begin{pmatrix}&lt;br /&gt;
    c_{11}\alpha_1 + c_{12}\alpha_2 \\&lt;br /&gt;
    c_{21}\alpha_1 + c_{22}\alpha_2&lt;br /&gt;
   \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Fallunterscheidung:'''&lt;br /&gt;
      &lt;br /&gt;
'''Fall 1:'''  C enthält genau 1 Fehler, z.B. &amp;lt;math&amp;gt;c_{11}&amp;lt;/math&amp;gt; hat falschen Wert&lt;br /&gt;
&lt;br /&gt;
:Der Fehler wird gefunden, wenn &amp;lt;math&amp;gt;\delta_1 \ne \gamma_1 \Leftrightarrow\alpha_1\ne 0&amp;lt;/math&amp;gt;. Da &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt; eine Zufallszahl aus &amp;lt;math&amp;gt;\{0,1\}&amp;lt;/math&amp;gt; ist, folgt daraus, dass '''p''' = '''q''' = &amp;lt;math&amp;gt;\frac{1}{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
       &lt;br /&gt;
'''Fall 2:'''  C enthält 2 Fehler&lt;br /&gt;
:(a)   in verschiedenen Zeilen und Spalten, z.B. &amp;lt;math&amp;gt;c_{11}&amp;lt;/math&amp;gt; und &amp;lt;math&amp;gt;c_{22}&amp;lt;/math&amp;gt;. Es gilt: Der Fehler in &amp;lt;math&amp;gt;c_{11}&amp;lt;/math&amp;gt; wird gefunden, wenn &amp;lt;math&amp;gt;\delta_1 \ne \gamma_1 \Leftrightarrow \alpha_1\ne 0&amp;lt;/math&amp;gt;. Unabhängig davon wird der Fehler in &amp;lt;math&amp;gt;c_{22}&amp;lt;/math&amp;gt; gefunden, wenn &amp;lt;math&amp;gt;\delta_2 \ne \gamma_2 \Leftrightarrow \alpha_2\ne 0&amp;lt;/math&amp;gt;. Da &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt; und &amp;lt;math&amp;gt;\alpha_2&amp;lt;/math&amp;gt; statistisch unabhängig sind, ist die Wahrscheinlichkeit für jedes dieser Ereignisse &amp;lt;math&amp;gt;q_1&amp;lt;/math&amp;gt; bzw. &amp;lt;math&amp;gt;q_2&amp;lt;/math&amp;gt; jeweils &amp;lt;math&amp;gt;\frac{1}{2}&amp;lt;/math&amp;gt;, und die Gesamtwahrscheinlichkeit '''q''', dass ''keiner'' der beiden Fehler gefunden wird, ist deren Produkt: '''q''' = &amp;lt;math&amp;gt;q_1*q_2 = \frac{1}{2}* \frac{1}{2} = \frac{1}{4}&amp;lt;/math&amp;gt;.        &lt;br /&gt;
&lt;br /&gt;
:(b) in verschiedenen Zeilen, gleichen Spalten, z.B. &amp;lt;math&amp;gt;c_{11}&amp;lt;/math&amp;gt; und &amp;lt;math&amp;gt;c_{21}&amp;lt;/math&amp;gt;. Es gilt: Der Fehler in &amp;lt;math&amp;gt;c_{11}&amp;lt;/math&amp;gt; wird gefunden, wenn &amp;lt;math&amp;gt;\delta_1 \ne \gamma_1 \Leftrightarrow \alpha_1\ne 0&amp;lt;/math&amp;gt;. Das gleiche gilt für den Fehler in &amp;lt;math&amp;gt;c_{21}&amp;lt;/math&amp;gt;. Die Wahrscheinlichkeit '''q''', dass ''keiner'' der beiden Fehler gefunden wird, ist demzufolge: '''q''' = &amp;lt;math&amp;gt;\frac{1}{2}&amp;lt;/math&amp;gt;.&lt;br /&gt;
               &lt;br /&gt;
:(c) in der gleichen Zeile, z.B. &amp;lt;math&amp;gt;c_{11}&amp;lt;/math&amp;gt; und &amp;lt;math&amp;gt;c_{12}&amp;lt;/math&amp;gt;. Es gilt: Der Fehler wird gefunden, wenn &amp;lt;math&amp;gt;\delta_1 \ne \gamma_1 \Leftrightarrow \alpha_1*c_{11}+\alpha_2*c_{12}\ne 0&amp;lt;/math&amp;gt;. Hier treten nun zwei ungünstige Fälle auf: &lt;br /&gt;
::1) Der Fehler wird u.a. dann nicht gefunden, wenn &amp;lt;math&amp;gt;\alpha_1 = \alpha_2=0&amp;lt;/math&amp;gt;. Die Wahrscheinlichkeit dafür ist  wieder '''q'''=&amp;lt;math&amp;gt;\frac{1}{4}&amp;lt;/math&amp;gt;&lt;br /&gt;
::2) &amp;lt;math&amp;gt;\alpha_1=\alpha_2=1&amp;lt;/math&amp;gt; (dies geschieht ebenfalls mit Wahrscheinlichkeit &amp;lt;math&amp;gt;\frac{1}{4}&amp;lt;/math&amp;gt;), aber die Werte &amp;lt;math&amp;gt;c_{11}&amp;lt;/math&amp;gt; und &amp;lt;math&amp;gt;c_{12}&amp;lt;/math&amp;gt; sind &amp;quot;zufälligerweise&amp;quot; so falsch, dass sich die Fehler gegenseitig aufheben. Die Wahrscheinlichkeit, dass beide Bedingungen gelten, ist auf jeden Fall '''q''' =  &amp;lt;math&amp;gt;\epsilon&amp;lt;\frac{1}{4}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Analog behandelt man die Fälle, dass C drei oder vier Fehler enthält. Fasst man die Fälle zusammen, ergibt sich, dass die Wahrscheinlichkeit, einen vorhandenen Fehler '''nicht''' zu entdecken, sicher kleiner als &amp;lt;math&amp;gt;\frac{1}{2}&amp;lt;/math&amp;gt; ist. Dies gilt auch allgemein:&lt;br /&gt;
&lt;br /&gt;
;Satz:&lt;br /&gt;
*Die Wahrscheinlichkeit, dass Freivalds Algorithmus einen vorhandenen Fehler '''nicht''' findet, ist '''q''' &amp;lt; &amp;lt;math&amp;gt;\frac{1}{2}&amp;lt;/math&amp;gt;. Wir haben diesen Satz oben für N=2 bewiesen, ein vollständiger Beweis findet sich in der [http://en.wikipedia.org/wiki/Freivald's_algorithm#Error_Analysis Wikipedia].&lt;br /&gt;
 &lt;br /&gt;
;Folgerung: &lt;br /&gt;
*Lässt man Freivalds Algorithmus mit verschiedenen &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt; k-mal laufen, gilt &amp;lt;math&amp;gt;q_k &amp;lt; 2^{-k}&amp;lt;/math&amp;gt; für die Wahrscheinlichkeit, dass '''keiner''' der k Durchläufe einen vorhandenen Fehler findet. Diese Wahrscheinlichkeit konvergiert sehr schnell gegen 0. Das heißt, der Algorithmus findet mit beliebig hoher Wahrscheinlichkeit ein Gegenbeispiel zu H (falls es eins gibt), wenn man ihn nur genügend oft mit jeweils anderen Zufallszahlen wiederholt. Daraus folgt, dass Testen ein effektives Fehlersuchverfahren sein kann -- die oben erwähnte Einschränkung von Dijktra trifft zwar zu, aber Tests, die mit so hoher Wahrscheinlichkeit funktionieren, sind für die Praxis meistens vollkommen ausreichend.&lt;br /&gt;
&lt;br /&gt;
=== Vergleich formaler Korrektheitsbeweis und Testen ===&lt;br /&gt;
&lt;br /&gt;
Nachdem nun die formalen Methoden sowie der Software-Test vorgesellt worden sind, ist nun die Frage, welcher der beiden Vorgänge der bessere ist, aufzugreifen. Allgemein gilt:&lt;br /&gt;
&lt;br /&gt;
;randomisierte Algorithmen&lt;br /&gt;
               &lt;br /&gt;
*sind schnell und einfach:&lt;br /&gt;
#da die Operationen einfach sind und wenig Zeit kosten&lt;br /&gt;
#des öfteren eine Auswahl vorgenommen wird ohne die Gesamtmenge näher zu betrachten&lt;br /&gt;
#die Auswahl selbst aufgrund einfacher Kriterien (bspw. zufällige Auswahl) erfolgt&lt;br /&gt;
*können Lösungen approximieren und liefern gute approximative Lösungen&lt;br /&gt;
&lt;br /&gt;
;formaler Korrektheitsbeweis mit deterministischen Algorithmen (siehe auch [http://de.wikipedia.org/wiki/Determinismus_(Algorithmus)])&lt;br /&gt;
  &lt;br /&gt;
*bei jedem Aufruf des Beweisers werden immer die selben Schritte durchlaufen&lt;br /&gt;
*keine Zufallswerte&lt;br /&gt;
*komplexer Aufbau&lt;br /&gt;
*oft sehr lange Laufzeit, z.B. mehrere Tage oder gar Monate&lt;br /&gt;
&lt;br /&gt;
Für die formalen Methoden spricht, dass man mit ihnen im Prinzip beweisen kann, dass H nun entweder tatsächlich falsch oder richtig ist. Die formalen Beweise bei realen Problemen sind allerdings so kompliziert, dass sie ebenfalls mit Computerhilfe erbracht werden müssen. Dadurch liegt auch hier keine 100%-ige Korrektheitsgarantie vor: Auch formale Methoden können zum falschen Ergebnis kommen, z.B. durch Hardwarefehler, Compilerbugs, oder unvorhergesehenes Umkippen von Bits (z.B. durch kosmische Strahlung -- diese Gefahr ist im Weltall sehr ernst zu nehmen). Die Möglichkeit von Hardwarefehlern wirkt sich auf die formalen Methoden wesentlich stärker aus, weil diese typischerweise wesentlich längere Laufzeiten haben als entsprechende Testalgorithmen. Es kann deshalb durchaus vorkommen, dass Tests eine höhere Erfolgswahrscheinlichkeit haben als ein formaler Beweis, wie die folgende Beispielrechnung zeigt. Wir nehmen an, dass die Hardware eine &amp;quot;Halbwertszeit&amp;quot; von 50 Millionen Sekunden hat, d.h. ein Hardwarefehler tritt im Durchschnitt etwa alle 20 Monate auf. Dann ist die Wahrscheinlichkeit, dass ein deterministischer Algorithmus '''nicht''' zum Ergebnis (oder zum falschen Ergebnis) kommt:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;math&amp;gt;q_{\mathrm{Beweis}} \approx 0.001&amp;lt;/math&amp;gt;, falls der Beweisalgorithmus 1 Tag benötigt,&lt;br /&gt;
* &amp;lt;math&amp;gt;q_{\mathrm{Beweis}} \approx 0.01&amp;lt;/math&amp;gt;, falls der Beweisalgorithmus 1 Woche benötigt,&lt;br /&gt;
* &amp;lt;math&amp;gt;q_{\mathrm{Beweis}} \approx 0.035&amp;lt;/math&amp;gt;, falls der Beweisalgorithmus 1 Monat benötigt.&lt;br /&gt;
&lt;br /&gt;
Zum Vergleich nehmen wir an, dass der entsprechende Softwaretest einmal pro Sekunde ausgeführt werden kann, und dass jeder Durchlauf den Fehler mit einer Wahrscheinlichkeit von &amp;lt;math&amp;gt;\frac{1}{2}&amp;lt;/math&amp;gt; '''nicht''' findet. Unter gleichzeitiger Berücksichtigung der Wahrscheinlichkeit von Hardwarefehlern gilt dann&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;math&amp;gt;q_{\mathrm{Test}} \approx 0.5&amp;lt;/math&amp;gt;, falls der Test 1-mal wiederholt wird,&lt;br /&gt;
* &amp;lt;math&amp;gt;q_{\mathrm{Test}} \approx 0.001&amp;lt;/math&amp;gt;, falls der Test 10-mal wiederholt wird,&lt;br /&gt;
* &amp;lt;math&amp;gt;q_{\mathrm{Test}} \approx 10^{-6}&amp;lt;/math&amp;gt;, falls der Test 100-mal wiederholt wird.&lt;br /&gt;
&lt;br /&gt;
Mit anderen Worten: hier ist das Testen vorzuziehen, weil es unter realistischen Bedingungen eine höhere Erfolgswahrscheinlichkeit hat als der formale Beweis. Leider gibt es bisher keine Theorie, mit deren Hilfe man für ein gegebenes Problem systematisch Tests konstruieren kann, deren Misserfolgswahrscheinlichkeit bei wiederholter Anwendung garantiert so schnell gegen Null konvergiert wie die des Freivalds Algorithmus. Dies ist ein offenes Problem der Informatik.&lt;br /&gt;
&lt;br /&gt;
==Anwendung des Softwaretestverfahren==&lt;br /&gt;
===Beispiel an Python-Code===&lt;br /&gt;
&lt;br /&gt;
Man betrachte die Aufgabe, aus einer Zahl x die Wurzel zu ziehen. Dies kann man erreichen, indem man mit Hilfe des Newtonschen Iterationsverfahrens eine Nullstelle des Polynoms &lt;br /&gt;
:&amp;lt;math&amp;gt;f(y) = x - y^2 = 0&amp;lt;/math&amp;gt; &lt;br /&gt;
sucht. Ist eine Näherungslösung &amp;lt;math&amp;gt;y^{(t)}&amp;lt;/math&amp;gt; bekannt, erhält man eine bessere Näherung durch&lt;br /&gt;
:&amp;lt;math&amp;gt;y^{(t+1)} = y^{(t)} - \frac{f(y^{(t)})}{f'(y^{(t)})}&amp;lt;/math&amp;gt;.&lt;br /&gt;
Mit &amp;lt;math&amp;gt;f\,'(y) = -2y&amp;lt;/math&amp;gt; wird das zu&lt;br /&gt;
:&amp;lt;math&amp;gt;y^{(t+1)} = y^{(t)} + \frac{x-(y^{(t)})^2}{2y^{(t)}}=\frac{y^{(t)}+x/y^{(t)}}{2}&amp;lt;/math&amp;gt;. &lt;br /&gt;
Im Spezialfall des Wurzelziehens war diese Newton-Iteration übrigens bereits im Altertum als [http://en.wikipedia.org/wiki/Babylonian_method#Babylonian_method Babylonische Methode] bekannt. Man kann dieselbe durch das folgende (allerding noch nicht korrekte) Pythonprogramm realisieren:&lt;br /&gt;
&lt;br /&gt;
           1   def sqrt(x):&lt;br /&gt;
           2       if (x&amp;lt;0):&lt;br /&gt;
           3           raise ValueError(&amp;quot;sqrt of negative number&amp;quot;)&lt;br /&gt;
           4       y = x / 2&lt;br /&gt;
           5       while y*y != x:&lt;br /&gt;
           6           y =(y + x/y) / 2&lt;br /&gt;
           7       return y:&lt;br /&gt;
&lt;br /&gt;
Für den oben aufgeführten Pythoncode können Tests mit Hilfe des Python-Moduls &amp;quot;[http://docs.python.org/lib/module-unittest.html unittest]&amp;quot; geschrieben werden (siehe auch Übungsaufgaben). Wir erklären hier die wichtigsten Befehle aus diesem Modul. Wir implementieren eine Testfunktionen (diese muss, wie im Python-Handbuch beschrieben, Methode einer Testklasse sein).&lt;br /&gt;
&lt;br /&gt;
   class SqrtTest(unittest.TestCase):&lt;br /&gt;
     def testsqrt(self): &lt;br /&gt;
         ...&lt;br /&gt;
&lt;br /&gt;
Zunächst muss man prüfen, ob die Vorbedingung korrekt getestet wird, d.h. ob bei einer negativen Zahl x eine Exception ausgelöst wird; dafür benötigt man &lt;br /&gt;
&lt;br /&gt;
         self.assertRaises(ValueError, sqrt, -1) &lt;br /&gt;
Sollte keine Exception vom Type &amp;lt;tt&amp;gt;ValueError&amp;lt;/tt&amp;gt; ausgelöst werden, dann würde der Test hier einen Fehler signalisieren. Dieser Test funktioniert aber.&lt;br /&gt;
&lt;br /&gt;
Weiter testen wir einige Beispiele, deren Wurzel wir kennen:&lt;br /&gt;
&lt;br /&gt;
         self.assertEqual(sqrt(9),3) &lt;br /&gt;
Wäre hier das Ergebnis ungleich 3, würde ebenfalls ein Fehler signalisiert, aber es funktioniert in unserem Falle. Der Test&lt;br /&gt;
&lt;br /&gt;
         self.assertEqual(sqrt(1),1)&lt;br /&gt;
schlägt jedoch mit &amp;lt;tt&amp;gt;ZeroDivisionError&amp;lt;/tt&amp;gt; fehl! Wir sehen, dass in Zeile 4 eine Ganzzahldivision durchgeführt wird, deren Ergebnis stets abgerundet wird, was hier zu &amp;lt;tt&amp;gt;y = 0&amp;lt;/tt&amp;gt; und damit zum Fehler in Zeile 6 führt. Wieso hat dann aber der erste Test &amp;lt;tt&amp;gt;sqrt(9) == 3&amp;lt;/tt&amp;gt; funktioniert? Hier gilt &amp;lt;tt&amp;gt;x / 2 == 4&amp;lt;/tt&amp;gt; und &amp;lt;tt&amp;gt;x / y == 2&amp;lt;/tt&amp;gt; (jeweils nach Abrunden), und der Mittelwert der beiden Schätzungen ist gerade &amp;lt;tt&amp;gt;y == 3&amp;lt;/tt&amp;gt;, also zufällig das richtige Ergebnis. Allgemein sehen wir jedoch, dass es nicht korrekt ist, mit ganzen Zahlen zu rechnen. Wir müssen also den Input zunächst in einen Gleitkommawert umwandeln:&lt;br /&gt;
&lt;br /&gt;
           1   def sqrt(x):&lt;br /&gt;
           1a      x = float(x)&lt;br /&gt;
           2       if (x&amp;lt;0):&lt;br /&gt;
           3           raise ValueError(&amp;quot;sqrt of negative number&amp;quot;)&lt;br /&gt;
           4       y = x / 2&lt;br /&gt;
           5       while y*y != x:&lt;br /&gt;
           6           y =(y + x/y) / 2&lt;br /&gt;
           7       return y:&lt;br /&gt;
&lt;br /&gt;
Jetzt funktionieren die vorhandenen Tests, aber bei anderen Zahlen (z.B. &amp;lt;tt&amp;gt;x = 1.21&amp;lt;/tt&amp;gt;) läuft das Programm in eine Endlosschleife. Dies liegt daran, dass durch die beschränkte Genauigkeit der Gleitkomma-Darstellung selten exakte Gleichheit in der &amp;lt;tt&amp;gt;while&amp;lt;/tt&amp;gt;-Bedingung erreicht wird. Man darf nicht auf Gleichheit prüfen, sondern muss den relativen Fehler beschränken:&lt;br /&gt;
&lt;br /&gt;
           1   def sqrt(x):&lt;br /&gt;
           1a      x = float(x)&lt;br /&gt;
           2       if (x&amp;lt;0):&lt;br /&gt;
           3           raise ValueError(&amp;quot;sqrt of negative number&amp;quot;)&lt;br /&gt;
           4       y = x / 2&lt;br /&gt;
           5       while abs(1.0 - x / y**2) &amp;gt; 1e-15:  # check for relative difference&lt;br /&gt;
           6           y =(y + x/y) / 2&lt;br /&gt;
           7       return y:&lt;br /&gt;
&lt;br /&gt;
Jetzt terminiert das Programm, aber der Test&lt;br /&gt;
&lt;br /&gt;
        self.assertEqual(sqrt(1.21)**2, 1.21)  # schlägt fehl&lt;br /&gt;
&lt;br /&gt;
schlägt wegen der beschränkten Genauigkeit der Gleitkommadarstellung fehl. Man umgeht dieses Problem, indem man im Tests selbst nur nähreungsweise Gleichheit fordert, z.B. auf 15 Dezimalstellen genau (bei 16 Dezimalen würde es nicht mehr funktionieren):&lt;br /&gt;
&lt;br /&gt;
        self.assertAlmostEqual(sqrt(1.21)**2, 1.21, 15)&lt;br /&gt;
&lt;br /&gt;
Wenden wir jetzt das ''Prinzip der Condition Coverage'' an (siehe unten), sehen wir, dass die &amp;lt;tt&amp;gt;while&amp;lt;/tt&amp;gt;-Bedingung bei allen bisherigen Tests zunächst mindestens einmal &amp;lt;tt&amp;gt;true&amp;lt;/tt&amp;gt; gewesen ist. Ein weiterer sinnvoller Tests ist deshalb einer, der diese Bedingung sofort &amp;lt;tt&amp;gt;false&amp;lt;/tt&amp;gt; macht. Dies trifft z.B. bei &amp;lt;tt&amp;gt;x == 4&amp;lt;/tt&amp;gt; zu, weil &amp;lt;tt&amp;gt;y = x / 2&amp;lt;/tt&amp;gt; hier gerade die korrekte Wurzel liefert. Wir fügen deshalb den Test&lt;br /&gt;
&lt;br /&gt;
         self.assertEqual(sqrt(4), 2) &lt;br /&gt;
&lt;br /&gt;
hinzu, der erfolgreich verläuft. Das ''Prinzip der Domänen-Zerlegung'' (siehe unten) führt uns weiter dazu, die Wurzel aus Null als sinnvollen Test zu betrachten, weil die Null am Rand des erlaubten Wertebereichs liegt. Der Test&lt;br /&gt;
&lt;br /&gt;
        self.assertEqual(sqrt(0), 0)  # schlägt fehl&lt;br /&gt;
&lt;br /&gt;
schlägt in der Tat mit einem &amp;lt;tt&amp;gt;ZeroDivisionError&amp;lt;/tt&amp;gt; fehl: In der Abfrage der &amp;lt;tt&amp;gt;while&amp;lt;/tt&amp;gt;-Bedingung wird jetzt durch &amp;lt;tt&amp;gt;y == 0&amp;lt;/tt&amp;gt; geteilt. Wir können diesen Fehler beheben, indem wir die Division aus der Bedingung eliminieren:&lt;br /&gt;
&lt;br /&gt;
           1   def sqrt(x):&lt;br /&gt;
           1a      x = float(x)&lt;br /&gt;
           2       if (x&amp;lt;0):&lt;br /&gt;
           3           raise ValueError(&amp;quot;sqrt of negative number&amp;quot;)&lt;br /&gt;
           4       y = x / 2&lt;br /&gt;
           5       while abs(y**2 - x) &amp;gt; 1e-15*x:  # check for relative difference without division&lt;br /&gt;
           6           y =(y + x/y) / 2&lt;br /&gt;
           7       return y:&lt;br /&gt;
&lt;br /&gt;
Damit ist auch dieses Problem behoben. Wir sehen also, wie das systematische Testen uns dabei hilft, Fehler im Programm zu finden und zu eliminieren.&lt;br /&gt;
&lt;br /&gt;
===Definition guter Tests===&lt;br /&gt;
&lt;br /&gt;
Wir haben gezeigt, dass Testen eine effektive Methode ist, um Fehler in Algorithmen zu finden. Allerdings gilt das nur, wenn Tests und Testdaten geschickt gewählt werden. Wir zeigen bewährte Methoden dafür. &lt;br /&gt;
&lt;br /&gt;
====Generieren von Referenzdaten====&lt;br /&gt;
&lt;br /&gt;
Wie immer man die Tests definiert hat, muss man am Ende die Ausgabe des Algorithmus mit dem korrekten Ergebnis vergleichen. Man bezeichnet ein bekanntes korrektes Ergebnis als ''Referenz-Ergebnis''. Dieses muss man aber erst einmal kennen, was sich mitunter als schwierig erweist. Folgende Verfahren haben sich als zweckmäßig erwiesen:&lt;br /&gt;
* Bei bestimmten Eingaben ist das Ergebnis für den Menschen einfach zu bestimmen, für den Algorithmus ist diese Eingabe aber ebenso schwierig wie jede andere. Dies gilt zum Beispiel für die Quadratzahlen im obigen Beispiel: der Algorithmus kennt keine Quadratzahlen und behandelt sie wie jede andere reelle Zahl. Deshalb eignen sich die Quadratzahlen zum Testen. Auch beim Sortieren kleiner Listen kann die korrekte Sortierung leicht bestimmt und als Referenz-Ergebnis abgespeichert werden. Der Test vergleicht dann einfach die Ausgabe des Sortieralgorithmus mit dem Referenz-Ergebnis.&lt;br /&gt;
* Oft kann man das korrekte Ergenis mit einem alternativen Verfahren berechnen. Dies gilt insbesondere, wenn man einen effizienten, aber komplizierten Algorithmus testen will. Dann berechnet man die Referenz-Ergebnisse mit einem langsamen, aber einfachen Verfahren. Dies ist möglich, weil man die Referenz-Ergebnisse ja abspeichern kann und der langsame Algorithmus daher nur wenige Male benutzt werden muss. Beispielsweise kann man einen komplizierten Sortieralgorithmus (Quicksort) mit Hilfe von selection sort testen.&lt;br /&gt;
* In vielen Fällen steht ein alternatives Programm zur Verfügung, z.B. eine ältere Version des zu testenden Programms, oder ein kommerzielles Programm (bzw. eine Demoversion), das dasselbe Problem löst, aber im aktuellen Kontext nicht verwendet werden kann (weil es z.B. zu teuer ist, oder nur auf einem Mac läuft). Diese Methode bietet sich auch an, wenn man einen Algorithmus aus einer Programmiersprache in eine andere portieren muss. &lt;br /&gt;
* Manchmal kann das korrekte Ergebnis nicht direkt angegeben werden, aber man kennt bestimmte Eigenschaften. Beim Sortieren kann man z.B. testen, dass kein Element des sortierten Arrays größer ist als das darauffolgende. Man testes also die Nachbedingungen. Eine abgeschwächte Versionen dieser Methode wird für randomisierte Algorithmen verwendet: Ist die Wahrscheinlichkeitsverteilung der Testeingaben bekannt, kann man die Wahrscheinlichkeitsverteilung der Ergebnisse, oder zumindest wichtige Eigenschaften wie z.B. den Mittelwert, mathematisch vorhersagen. Der Test ermittelt dann, ob die Ausgaben über viele Durchläufe des Algorithmus diese statistischen Eigenschaften aufweisen.&lt;br /&gt;
&lt;br /&gt;
====Arten von Tests====&lt;br /&gt;
&lt;br /&gt;
Man unterscheidet 3 grundlegende Arten von Tests:&lt;br /&gt;
&lt;br /&gt;
;Black-box Tests [http://en.wikipedia.org/wiki/Black_box_testing]: Hier ist dem Tester nur die Spezifikation, aber nicht die Implementation des Algorithmus bekannt. Alle Tests sowie die Eingaben und Referenz-Ergebnisse müssen aus der Spezifikation abgeleitet werden. Die automatisierte Generierung guter Tests aus der Spezifikation ist ein aktives Forschungsgebiet.&lt;br /&gt;
;Gray-box Tests (auch Glass-box Tests) [http://www.cse.fau.edu/~maria/COURSES/CEN4010-SE/C13/glass.htm]: Hier kennt der Tester auch die Implementation und kann dadurch Tests entwerfen, die für diese spezielle Implementation besonders aussagekräftig sind. Es besteht allerdings die Gefahr, dass der Tester nicht mehr unvoreingenommen an das Testproblem herangeht, und Zustände, die seiner Meinung nach gar nicht vorkommen können, auch nicht testet (erst später stellt sich heraus, dass diese Zustände doch vorkommen).&lt;br /&gt;
;White-box Tests [http://en.wikipedia.org/wiki/White_box_testing]: Hier kann der Tester die Implementation sogar in geeigneter Weise verändern, z.B. &lt;br /&gt;
:* explizite Tests für Vor- und Nachbedingungen (&amp;quot;Assertions&amp;quot;) einbauen. Dies bietet sich insbesondere in der alpha- und beta-Testphase eines Programms an, um Fehler schnell zu lokalisieren. Auch die unter Windows bekannte Dialogbox &amp;quot;Diesen Fehler bitte auch an Microsoft melden&amp;quot; wird durch solche eingebauten Assertions ausgelöst, wenn das Programm in einen illegalen Zustand geraten ist und abgebrochen werden muss.&lt;br /&gt;
:* zusätzlichen Code einbauen, der feststellt, ob alle Teile des Programms auch tatsächlich getestet wurden (&amp;quot;[http://blogs.msdn.com/phuene/archive/2007/05/03/code-coverage-instrumentation.aspx code coverage instrumentation]&amp;quot;). Dieser Code gibt nach dem Testen z.B. aus, welche Programmzeilen von keinem existierenden Test aufgerufen worden sind. Wenn der ausgeführte Code sehr stark von den Daten abhängt (z.B. bei interaktiven Programmen), kann es sehr schwierig sein, die ''coverage'' auf andere Weise festzustellen.&lt;br /&gt;
:* absichtlich Bugs einbauen (die automatisch wieder abgeschaltet werden, wenn das Testen vorbei ist). Durch diese &amp;quot;[http://en.wikipedia.org/wiki/Fault_injection fault injection]&amp;quot; kann man herausfinden, ob die Tests mächtig genug sind, vorhandene Bugs zu finden.&lt;br /&gt;
&lt;br /&gt;
====Prinzipien für die Generierung von Testdaten====&lt;br /&gt;
&lt;br /&gt;
;Prinzip der Regressionstests (&amp;quot;[http://en.wikipedia.org/wiki/Regression_testing Regression testing]&amp;quot;): Häufig werden Tests während der Programmentwicklung verwendet, um einen Algorithmus zu debuggen. Sobald der Algorithmus aber funktioniert werden die Tests gelöscht, denn sie werden ja jetzt nicht mehr gebraucht. Dies ist ein schwerwiegender ''Fehler'': Jedes erfolgreiche Programm muss früher oder später weiterentwickelt werden (zumindest die Anpassung an eine neue Betriebssystemversion ist ab und zu notwendig). Jede Änderung birgt aber die Gefahr, dass sich neue Bugs in bisher funktionierenden Code einschleichen. Man sollte deshalb alle Tests aufheben und in einer ''test suite'' sammeln. Durch diese &amp;quot;regression tests&amp;quot; kann man nach jeder Änderung feststellen, ob die alte Funktionalität noch intakt ist, und gegebenenfalls die letzte Änderung einfach rückgängig machen. Tut man dies nicht, kann die Gefahr von unbeabsichtigten destruktiven Änderungen so groß werden, dass das Programm gar nicht mehr weiterentwickelt werden kann. Dies wird drastisch durch den bekannten Spruch &amp;quot;never change a running program&amp;quot; ausgedrückt.&lt;br /&gt;
&lt;br /&gt;
;Prinzip der äquvalenten Eingaben (Domain Partitioning oder Equivalence Partitioning) [http://en.wikipedia.org/wiki/Equivalence_partitioning]: Für ähnliche Eingaben verhält sich ein Algorithmus normalerweise ähnlich, und es hat keinen Sinn, alle diese Eingaben zu testen. Statt dessen teilt (partitioniert) man die Eingabedomäne in Äquivalenzklassen, die vom Algorithmus im wesentlichen gleich behandelt werden. Im obigen Beispiel der Wurzelberechnung ergeben sich zwei Klassen aus der Spezifikation: die negativen Zahlen (für die die Wurzel undefiniert ist und deshalb ein Fehler signalisiert werden muss) und die nicht-negativen Zahlen. Wenn man auch den Quellcode kennt (gray-box testing), kann man die Eingaben oft feiner unterteilen. Z.B. werden häufig unterschiedliche Algorithmen für kleine und für große Eingaben benutzt. Viele Quicksort-Implementationen verwenden beispielsweise für Arrays mit höchstens vier Elementen ein explizites Sortierverfahren, für Arrays der Länge 5 bis 25 selection sort, und erst für größere Arrays das eigentliche Quicksort. Aus der Einteilung der Eingabedomäne ergeben sich zwei wichtige Regeln für die Wahl der Testdaten:&lt;br /&gt;
:* Aus jeder Äquivelenzklasse wählt man mindestens einen typischen Vertreter, um das normale Verhalten des Algorithmus in jedem Fall zu testen.&lt;br /&gt;
:* Aus jeder Äquivelenzklasse wählt man Randwerte, weil gerade bei diesen Werten am häufigsten Fehler gemacht werden. Im obigen Wurzelbeispiel ist der Randwert die Null, die in der Tat in einer Version des Algorithmus zu einem &amp;lt;TT&amp;gt;ZeroDivisionError&amp;lt;/tt&amp;gt; geführt hat. Andere typische Randfehler sind, dass Randelemente dem falschen Algorithmenzweig zugeordnet werden (z.B. wenn bei unserem Wurzelbeispiel die Abfrage am Anfang &amp;lt;tt&amp;gt;if x &amp;lt;= 0:&amp;lt;/tt&amp;gt; statt &amp;lt;tt&amp;gt;if x &amp;lt; 0:&amp;lt;/tt&amp;gt; gewesen wäre), dass Schleifen um einen Index zu spät beginnen oder zu früh abbrechen (&amp;quot;[http://en.wikipedia.org/wiki/Off-by-one_error Off-by-one errors]&amp;quot;), oder dass ein seltener Randfall gar nicht implementiert ist und einfach zum Absturz führt.&lt;br /&gt;
&lt;br /&gt;
;Prinzip, den Fehler zu reproduzieren (Failure Reproduction): Wenn ein Bug gemeldet wird, welches die Tests bisher übersehen haben, fügt man einen Test hinzu, der dieses Bug findet. Im Zusammenhang mit regression tests ist damit sichergestellt, dass dasselbe Bug nicht noch einmal auftreten kann.&lt;br /&gt;
&lt;br /&gt;
;Prinzip der Code Coverage [http://en.wikipedia.org/wiki/Code_coverage]: Hier stellt man sicher, dass tatsächlich der gesamte Code (oder ein vorher festgelegter hoher Prozentsatz) gestestet wurde. Gerade bei komplizierten interaktiven Programmen ist diese &amp;quot;code coverage&amp;quot; mitunter nicht leicht zu erreichen, weil manche Programmteile nur bei sehr seltenen oder obskuren Eingaben ausgeführt werden. Eine minimale code coverage erreicht man allerdings bereits, wenn man in einem black-box-Test die Testdaten nach dem Prinzip der äquivalenten Eingaben auswählt, weil dann aus jeder Äquivalenzklasse mindestens ein Vertreter getestet wird. Im Allgemeinen muss man aber den Quellcode zumindest kennen (gray-box-Test), um geeignete Testdaten für code coverage zu identifizieren. Code coverage kann in verschiednen Graden angestrebt werden&lt;br /&gt;
:* Function coverage: Jede Funktion eines Programms sollte mindestens einmal aufgerufen werden.&lt;br /&gt;
:* Statement coverage: Jedes Statement (d.h. im wesentlichen jede Programmzeile) sollte mindestens einmal ausgeführt werden. Im obigen Wurzelbeispiel erfordert dies, dass z.B. mindestens einmal eine negative Zahl getestet wird, um die Exception zu prüfen.&lt;br /&gt;
:* Condition coverage: Jede Bedingung (explizit in &amp;lt;tt&amp;gt;if&amp;lt;/tt&amp;gt;-Bedingungen, implizit in den Abbruchbedingungen von &amp;lt;tt&amp;gt;for&amp;lt;/tt&amp;gt;- und &amp;lt;tt&amp;gt;while&amp;lt;/tt&amp;gt;-Schleifen) sollte mindestens einmal mit dem Ergebnis &amp;lt;tt&amp;gt;True&amp;lt;/tt&amp;gt; und einmal mit dem Ergebnis &amp;lt;tt&amp;gt;False&amp;lt;/tt&amp;gt; durchlaufen werden. Im Wurzelbeispiel haben wir die Eingabe &amp;lt;tt&amp;gt;x = 4&amp;lt;/tt&amp;gt; gewählt, damit die &amp;lt;tt&amp;gt;while&amp;lt;/tt&amp;gt;-Schleife auch einmal beim ersten Aufruf sofort &amp;lt;tt&amp;gt;False&amp;lt;/tt&amp;gt; liefert.&lt;br /&gt;
:* Path coverage: Jeder Programmpfad (d.h. jede Kombination von Wahrheitswerten bei allen Bedingungen) sollte einmal ausgeführt werden. Dies ist im Allgemeinen unerreichbar, weil es unendlich viele, oder zumindest zu viele verschiedene Pfade gibt.&lt;br /&gt;
:Die Qualität der Tests steigt, wenn eine hohe Coverage (am besten 100%) erreicht wird, und/oder man eine mächtigere Art von Coverage fordert.&lt;br /&gt;
&lt;br /&gt;
;Prinzip der erschöpfenden Tests: Wenn ein Algorithmus nur wenige mögliche Eingaben hat, kann man sämtliche Eingaben testen. Bei sehr wichtigen Algorithmen kann das auch dann noch sinnvoll sein, wenn es relativ viele mögliche Eingaben gibt. In den meisten Fällen ist es jedoch zu aufwändig.&lt;br /&gt;
&lt;br /&gt;
;Prinzip der vollständigen Paarung (Pair-wise coverage) [http://citeseer.ist.psu.edu/78354.html]: Wenn ein Algorithmus N Eingabeparameter hat, und jeder Parameter hat K&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; mögliche Werte, müssen bei der erschöpfenden Suche K&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;*...*K&amp;lt;sub&amp;gt;N&amp;lt;/sub&amp;gt; Kombinationen getestet werden. Beschränkt man sich in jedem Parameter auf typische Werte und Randwerte jeder Äquivalenzklasse, kann man K&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; zwar drastisch reduzieren, aber das Produkt K&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;*...*K&amp;lt;sub&amp;gt;N&amp;lt;/sub&amp;gt; wird immer noch sehr groß (bei 4 Parametern und nur 3 möglichen Werten pro Parameter hat man bereits 3&amp;lt;sup&amp;gt;4&amp;lt;/sup&amp;gt;=81 mögliche Kombinationen). Sei v&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt; der j-te Wert des Parameters i. Anstatt zu versuchen, alle Kombinationen zu testen, kann man fordern, dass zumindest alle möglichen Paare v&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt; und v&amp;lt;sub&amp;gt;mj&amp;lt;/sub&amp;gt; (i&amp;amp;ne;m) in mindestens einem Test vorkommen. Gibt es nur zwei Parameter, gewinnt man durch diese Einschränkung natürlich nichts, denn man muss mindestens K&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;*K&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; Tests durchführen. Hat man jedoch 3 Parameter, kann man mit weniger Tests auskommen als zuvor, da jeder Test bis zu drei verschiedene Paarungen abdecken kann (eine für den ersten und zweiten Parameter, eine für den ersten und dritten, eine für den zweiten und dritten). Bei vier Parametern werden sogar sechs Paarungen pro Test abgearbeitet usw. Die Theorie des &amp;quot;experimental design&amp;quot; beschreibt nun, wie man systematisch alle möglichen Paarungen mit möglichst wenigen Tests erzeugt. Es stellt sich heraus, dass man alle Paarungen von 3, 4 oder mehr Parametern oft mit genauso vielen Tests erzeugen kann wie bei 2 Parametern nötig wären. Dazu verwendet man die Methode der [http://en.wikipedia.org/wiki/Latin_square Latin Squares].  Wir beschreiben diese Methode für den einfachen Fall von 3 möglichen Werten pro Parameter.&lt;br /&gt;
&lt;br /&gt;
:Ein Latin Square der Größe 3 ist eine 3x3 Matrix, deren Einträge die Zahlen 1...3 sind, und zwar so, dass jede Zahl genau einmal in jeder Zeile und Spalte vorkommt (ähnlich wie beim Sudoku). Eine mögliche Matrix ist z.B.&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;P=\begin{pmatrix}1 &amp;amp; 2 &amp;amp; 3 \\&lt;br /&gt;
                      2 &amp;amp; 3 &amp;amp; 1 \\&lt;br /&gt;
                      3 &amp;amp; 1 &amp;amp; 2\end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
:Man bildet jetzt 9 Kombinationen der Zahlen 1...3, indem man zeilenweise durch die Matrix P geht, und den Zeilenindex (die Nummer der aktuellen Zeile) als erste Zahl, den Spaltenindex als zweite Zahl, und den Eintrag an der aktuallen Position als dritte Zahl verwendet. Man erhält&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;0&amp;quot; cellpadding=&amp;quot;7&amp;quot; align=&amp;quot;center&amp;quot;&lt;br /&gt;
|-align=&amp;quot;center&amp;quot; &lt;br /&gt;
|&lt;br /&gt;
! Komb. 1&lt;br /&gt;
! Komb. 2&lt;br /&gt;
! Komb. 3&lt;br /&gt;
! Komb. 4&lt;br /&gt;
! Komb. 5&lt;br /&gt;
! Komb. 6&lt;br /&gt;
! Komb. 7&lt;br /&gt;
! Komb. 8&lt;br /&gt;
! Komb. 9&lt;br /&gt;
|-&lt;br /&gt;
!Zahl 1 (Zeilenindex)&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|-&lt;br /&gt;
! Zahl 2 (Spaltenindex)&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|-&lt;br /&gt;
! Zahl 3 (aktueller Matrixeintrag von P)&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
:Diese Tabelle bestimmt, welcher Wert in jedem Test für jeden Parameter verwendet wird. Z.B. wird der erste Test mit v&amp;lt;sub&amp;gt;11&amp;lt;/sub&amp;gt; (erster Wert des ersten Parameters), v&amp;lt;sub&amp;gt;21&amp;lt;/sub&amp;gt; (erster Wert des zweiten Parameters), v&amp;lt;sub&amp;gt;31&amp;lt;/sub&amp;gt; (erster Wert des dritten Parameters) aufgerufen&lt;br /&gt;
       assertEqual( foo(v11, v21, v31), foo_reference1)&lt;br /&gt;
(reference1 ist das korrekte Referenz-Ergebnis für diese Prameterbelegung). Der letzte Test hat die Parameter v&amp;lt;sub&amp;gt;13&amp;lt;/sub&amp;gt;, v&amp;lt;sub&amp;gt;23&amp;lt;/sub&amp;gt;, v&amp;lt;sub&amp;gt;32&amp;lt;/sub&amp;gt;&lt;br /&gt;
       assertEqual( foo(v13, v23, v32), foo_reference9)&lt;br /&gt;
:Man überzeugt sich leicht, dass diese 9 Tests jede mögliche Paarung genau einmal enthalten. Hat der Algorithmus 4 Parameter, benötigt man einen zweiten Latin Square, der zum ersten orthogonal ist. Zwei Latin Squares P und Q heißen orthogonal, wenn alle Paare c&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt;=(P&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt;, Q&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt;) eindeutig sind, d.h. es gilt c&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt;&amp;amp;ne;c&amp;lt;sub&amp;gt;kl&amp;lt;/sub&amp;gt; falls i&amp;amp;ne;k und j&amp;amp;ne;l. Ein zu dem obigen P orthogonales Q ist z.B.&lt;br /&gt;
:&amp;lt;math&amp;gt;Q=\begin{pmatrix}1 &amp;amp; 2 &amp;amp; 3 \\&lt;br /&gt;
                        3 &amp;amp; 1 &amp;amp; 2 \\&lt;br /&gt;
                        2 &amp;amp; 3 &amp;amp; 1\end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
: Jetzt bildet man Kombinationen aus 4 Zahlen, indem man zur obigen Tabelle noch eine vierte Zeile hinzufügt, die die aktuellen Einträge von Q für den jeweiligen Zeilen- und Spaltenindex enthält:&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;0&amp;quot; cellpadding=&amp;quot;7&amp;quot; align=&amp;quot;center&amp;quot;&lt;br /&gt;
|-align=&amp;quot;center&amp;quot; &lt;br /&gt;
|&lt;br /&gt;
! Komb. 1&lt;br /&gt;
! Komb. 2&lt;br /&gt;
! Komb. 3&lt;br /&gt;
! Komb. 4&lt;br /&gt;
! Komb. 5&lt;br /&gt;
! Komb. 6&lt;br /&gt;
! Komb. 7&lt;br /&gt;
! Komb. 8&lt;br /&gt;
! Komb. 9&lt;br /&gt;
|-&lt;br /&gt;
!Zahl 1 (Zeilenindex)&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|-&lt;br /&gt;
! Zahl 2 (Spaltenindex)&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|-&lt;br /&gt;
! Zahl 3 (aktueller Matrixeintrag von P)&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|-&lt;br /&gt;
! Zahl 4 (aktueller Matrixeintrag von Q)&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
:Es sind immer noch nur 9 Tests nötig, um alle Paarungen zu erzeugen. Der erste und letzte Test sind nun:&lt;br /&gt;
       assertEqual( bar(v11, v21, v31, v41), bar_reference1)&lt;br /&gt;
       ...&lt;br /&gt;
       assertEqual( bar(v13, v23, v32, v41), bar_reference9)&lt;br /&gt;
:Die Methode der Latin Squares  funktioniert auch, wenn mehr als 3 Belegungen für jeden Parameter möglich sind, und wenn es mehr als 4 Parameter gibt. Für die Einzelheiten verweisen wir auf die Literatur, z.B. [http://citeseer.ist.psu.edu/78354.html], [http://en.wikipedia.org/wiki/Latin_square]. Empirische Untersuchungen haben ergeben, dass die Methode der vollständigen Paarung oft über 90% der Fehler in einem Programm finden kann.&lt;/div&gt;</summary>
		<author><name>Jschleic</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Korrektheit&amp;diff=2524</id>
		<title>Korrektheit</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Korrektheit&amp;diff=2524"/>
				<updated>2008-07-22T08:59:17Z</updated>
		
		<summary type="html">&lt;p&gt;Jschleic: typos&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Man unterscheidet zwischen Prüfung der Korrektheit (Verifikation) und Prüfung der Spezifikation (Validierung). Ein Algorithmus heißt korrekt, wenn er sich gemäß seiner Spezifikation verhält, auch wenn seine Spezifikation nicht immer die gewünschten Ergebnisse liefert. Der Spezifikation beschreibt die Vorbedingungen (was vor der Anwendung des Algorithmus gilt, so dass der Algorithmus überhaupt angewendet werden darf) und die Nachbedingungen (was nach der Anwendung des Algorithmus gilt, welchen Zustand des Systems der Algorithmus also erzeugt). Hier geht es ausschliesslich um die Prüfung der Korrektheit eines Algorithmus, also darum, ob die spezifizierten Nachbedingungen wirklich gelten.&lt;br /&gt;
 &lt;br /&gt;
Nebenbemerkungen&lt;br /&gt;
# es gibt Algorithmen, die ''nie'' mit einer 100-prozentigen Wahrscheinlichkeit richtige Ergebnisse liefern können (z.B. [http://en.wikipedia.org/wiki/Primality_test#Probabilistic_tests nichtdeterministische Primzahltests]). &lt;br /&gt;
# '''Korrektheit''' wird in Algorithmenbüchern meist nur im Zusammenhang mit konkreten Algorithmen behandelt, aber nicht als übergreifendes Problem. Dies erscheint der Bedeutung von Korrektheit nicht angemessen.&lt;br /&gt;
&lt;br /&gt;
Will man die Korrektheit eines Algorithmus/Programms feststellen, hat man 3 Vorgehensweisen zur Verfügung: Prüfung der syntaktischen Korrektheit, formaler Korrektheitsbeweis und Softwaretest.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Syntaktische Korrektheit ==&lt;br /&gt;
&lt;br /&gt;
Die syntaktische Korrektheit behandeln wir hier nur kurz und der Vollständigkeit halber. Sie wird in den Veranstaltungen zur theoretischen Informatik (Grammatiken) und zum Compilerbau ausführlich behandelt.&lt;br /&gt;
&lt;br /&gt;
=== Syntaktische Prüfung ===&lt;br /&gt;
Es wird eine Grammatik definiert, deren Regeln die Implementation des Algorithmus befolgen muss. Für ein Programm heißt das beispielsweise, dass die Syntax der Programmiersprache eingehalten werden muss.&lt;br /&gt;
&lt;br /&gt;
Vorteile des Verfahrens: die Richtigkeit der Syntax lässt sich leicht vom Compiler/Interpreter überprüfen (mehr dazu in der Theoretischen Informatik und Compilerbau). Somit ist es die einfachste Möglichkeit, viele inkorrekte Programme schnell zu erkennen und zurückzuweisen.&lt;br /&gt;
  &amp;gt;&amp;gt;&amp;gt; if a==0&lt;br /&gt;
    File &amp;quot;&amp;lt;stdin&amp;gt;&amp;quot;, line 1&lt;br /&gt;
      if a==0&lt;br /&gt;
            ^&lt;br /&gt;
  SyntaxError: invalid syntax&lt;br /&gt;
&lt;br /&gt;
=== Typprüfung ===&lt;br /&gt;
Ein Typ definiert Gruppierung der Daten und die Operationen, die für diese Datengruppierung erlaubt sind(konkreter Typ) bzw. die Bedeutung der Daten und die erlaubten Operationen (abstrakter Datentyp, vgl. Dreieck aus der [[Einführung#Definition von Datenstrukturen|ersten Vorlesung]]). Typen sind Zusicherungen an den Algorithmus und den Compiler/Interpreter, dass Daten und deren Operationen bestimmte semantische Bedingungen einhalten. Wenn man innerhalb des Algorithmus mit Typen arbeitet, darf man von der semantischen Korrektheit der erlaubten Operationen ausgehen. Umgekehrt können Operationen, die zu Typkonflikten führen würden, leicht als inkorrekt zurückgeweisen werden.&lt;br /&gt;
&lt;br /&gt;
Vorteile des Verfahrens: Typprüfung ist teuerer als syntaktische Prüfung, aber billiger als andere Prüfungen der Korrektheit (mehr dazu im Kapitel [[Generizität]]).&lt;br /&gt;
  &amp;gt;&amp;gt;&amp;gt; a=3&lt;br /&gt;
  &amp;gt;&amp;gt;&amp;gt; b=None&lt;br /&gt;
  &amp;gt;&amp;gt;&amp;gt; a+b&lt;br /&gt;
  Traceback (most recent call last):&lt;br /&gt;
    File &amp;quot;&amp;lt;stdin&amp;gt;&amp;quot;, line 1, in &amp;lt;module&amp;gt;&lt;br /&gt;
  TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'&lt;br /&gt;
&lt;br /&gt;
In python ist (ebenso wie in vielen anderen Programmiersprachen) explizite Typprüfung möglich:&lt;br /&gt;
  &amp;gt;&amp;gt;&amp;gt; import types&lt;br /&gt;
  &amp;gt;&amp;gt;&amp;gt; a=3&lt;br /&gt;
  &amp;gt;&amp;gt;&amp;gt; b=None&lt;br /&gt;
  &amp;gt;&amp;gt;&amp;gt; if isinstance(b, types.IntType): # prüft, ob b ein Integer ist&lt;br /&gt;
  ...     print a+b&lt;br /&gt;
  ... else:&lt;br /&gt;
  ...     raise TypeError, &amp;quot;b ist kein Integer&amp;quot; # falls b kein Integer ist, wird ein TypeError ausgelöst&lt;br /&gt;
  ... &lt;br /&gt;
 &lt;br /&gt;
  Traceback (most recent call last):&lt;br /&gt;
    File &amp;quot;&amp;lt;stdin&amp;gt;&amp;quot;, line 4, in &amp;lt;module&amp;gt;&lt;br /&gt;
  TypeError: b ist kein Integer&lt;br /&gt;
&lt;br /&gt;
== Formaler Korrektheitsbeweis ==&lt;br /&gt;
=== (Halb-)Automatisches Beweisen ===&lt;br /&gt;
Man versucht, die Hypothese H: ''Algorithmus ist korrekt'' entweder mathematisch zu beweisen oder zu widerlegen. Dieses Beweisverfahren heißt dann halbautomatisch, wenn der Mensch in den Entscheidungsprozess miteinbezogen wird.&lt;br /&gt;
&lt;br /&gt;
Um den Beweis durchführen zu können, ist folgendes nötig:&lt;br /&gt;
;eine [http://en.wikipedia.org/wiki/Formal_specification formale Spezifikation] des Algorithmus: eine formale Spezifikation wird in einer [http://en.wikipedia.org/wiki/Specification_language Spezifikationssprache] geschrieben (z.B. [http://en.wikipedia.org/wiki/Z_notation Z]). Sie ist &lt;br /&gt;
:* deklarativ (d.h. beschreibt, was das Programm tun soll, ist selbst aber nicht ausführbar)&lt;br /&gt;
:* formal präzise (kann nur auf eine einzige Weise interpretiert werden)&lt;br /&gt;
:* hierarchisch aufgebaut (eine Spezifikation für einen komplizierten Algorithmus greift auf Spezifikationen für einfache Bestandteile dieses Algorithmus zurück)&lt;br /&gt;
:* so einfach, dass ihre Korrektheit für einen Menschen mit entsprechender Erfahrung unmittelbar einsichtig ist (denn eine Spezifikation kann nicht formal bewiesen werden - dafür wäre eine weitere Spezifikation nötig, die auch bewiesen werden müsste usw.)&lt;br /&gt;
;ein axiomatisiertes Programmiermodell: zum Beispiel&lt;br /&gt;
:* eine axiomatisierbare Programmiersprache, wie z.B. WHILE-Programm (s. [[Einführung#Zur Frage der elementaren Schritte|erste Vorlesung]]), Pascal (siehe dazu Hoare's [http://delivery.acm.org/10.1145/70000/63445/cb-p153-hoare.pdf?key1=63445&amp;amp;key2=5041959021&amp;amp;coll=ACM&amp;amp;dl=ACM&amp;amp;CFID=15151515&amp;amp;CFTOKEN=6184618 grundlegenden Artikel]) und rein funktionale Programmiersprachen&lt;br /&gt;
:* ein axiomatisierbares Subset einer Programmiersprache (die meisten Programmiersprachen sind zu komplex, um als Ganzes axiomatisierbar zu sein)&lt;br /&gt;
:* endliche Automaten&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Der Korrektheitsbeweis kann beispielsweise mit dem Hoare-Kalkül (Hoare-Logik) durchgeführt werden (Hoare erfand u.a. den Quicksort-Algorithmus). Diese Methode wurde in &lt;br /&gt;
:  C.A.R. Hoare: ''&amp;quot;An Axiomatic Basis for Computer Programming&amp;quot;'', Communications of the ACM, 1969 [http://www.cs.ucsb.edu/~kemm/courses/cs266/hoare69.pdf] &lt;br /&gt;
erstmalig beschrieben. Im folgenden wird das Verfahren an einem Beispiel erläutert.&lt;br /&gt;
&lt;br /&gt;
==== Beispiel-Algorithmus ====&lt;br /&gt;
Zuerst brauchen wir einen Algorithmus, den wir auf Korrektheit prüfen wollen. Wir nehmen als Beispiel die Division x/y durch sukzessives Subtrahieren.&lt;br /&gt;
&lt;br /&gt;
 Vorbedingungen:&lt;br /&gt;
    int x,y&lt;br /&gt;
   0 &amp;lt; y &amp;lt;= x&lt;br /&gt;
 Gesucht:&lt;br /&gt;
    Quotient q, Rest r&lt;br /&gt;
 Algorithmus:&lt;br /&gt;
    r = x&lt;br /&gt;
    q = 0&lt;br /&gt;
    while y &amp;lt;= r:&lt;br /&gt;
        r = r - y&lt;br /&gt;
        q = q + 1&lt;br /&gt;
 Nachbedingungen:&lt;br /&gt;
    x == r + y*q and r &amp;lt; y&lt;br /&gt;
&lt;br /&gt;
==== Aufbau der Hoare-Logik ====&lt;br /&gt;
&lt;br /&gt;
Grundlegende syntaktische Struktur:&lt;br /&gt;
: p {Q} r&lt;br /&gt;
mit '''p''':Vorbedingung, '''Q''': Operation, '''r''': Nachbedingung.&lt;br /&gt;
Es bedeutet also schlicht: wenn man im Zustand '''p''' ist und eine Operation '''Q''' ausführt, kommt man in den Zustand '''r'''. Hat eine Operation keine Vorbedingung, schreibt man &lt;br /&gt;
: true {Q} r&lt;br /&gt;
&lt;br /&gt;
Die Hoare-Logik besteht aus 5 Axiomen:&lt;br /&gt;
;D0 - Axiom der Zuweisung: (Rule of Assignment)&lt;br /&gt;
:: R[t] {x=t} R[x]&lt;br /&gt;
  &lt;br /&gt;
: '''Beispiel:''' t==5 {x=t} x==5&lt;br /&gt;
&lt;br /&gt;
:Vorbedingung und Nachbedingung sind gleich, mit Ausnahme der Variablen x und t, die in der Zuweisung verknüpft werden: Man erhält die Vorbedingung, wenn man in der Nachbedingung alle Vorkommen von x (bzw. allgemein: alle Vorkommen der linken Variable der Zuweisung) durch t (bzw. allgemein: durch die rechte Variable der Zuweisung) ersetzt.&lt;br /&gt;
&lt;br /&gt;
;D1 - Konsequenzregeln: (Rules of Consequence, besteht aus zwei Axiomen)&lt;br /&gt;
:'''D1(a):''' wenn gilt&lt;br /&gt;
:: P {Q} R und R &amp;amp;rArr; S&lt;br /&gt;
:dann gilt auch&lt;br /&gt;
:: P {Q} S&lt;br /&gt;
:'''D1(b):''' wenn gilt &lt;br /&gt;
:: P {Q} R und S &amp;amp;rArr; P&lt;br /&gt;
:dann gilt auch&lt;br /&gt;
:: S {Q} R&lt;br /&gt;
:'''Beispiel:''' Für jede ganze Zahl gilt (x&amp;gt;5) &amp;amp;rArr; (x&amp;gt;0). Gilt außerdem (x&amp;gt;5) dann gilt erst recht (x&amp;gt;0).&lt;br /&gt;
&lt;br /&gt;
;D2 - Sequenzregel: (Rule of Composition)&lt;br /&gt;
:wenn gilt&lt;br /&gt;
:: P {Q&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;} R&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; und R&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; {Q&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;} R &lt;br /&gt;
:dann gilt auch&lt;br /&gt;
:: P {Q&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;, Q&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;} R&lt;br /&gt;
:Das heißt: wenn man P hat und Q&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; darauf anwendet, kommt man zu R&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;. Wenn man R&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; hat und Q&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; darauf anwendet, kommt man zu R. Deshalb kann man das so verkürzen: wenn man P hat und nacheinander Q&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; und Q&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; darauf anwendet, kommt man zu R.&lt;br /&gt;
&lt;br /&gt;
;D3 - Iterationsregel: (Rule of Iteration)&lt;br /&gt;
:wenn gilt&lt;br /&gt;
:: (P &amp;amp;and; B) {S} P&lt;br /&gt;
:dann gilt auch&lt;br /&gt;
:: P { while B do S } (&amp;amp;not;B &amp;amp;and; P)&lt;br /&gt;
:P wird dabei als '''Schleifeninvariante''' bezeichnet, weil es sowohl in der Vor- als auch in der Nachbedingung gilt. B ist die '''Schleifenbedingung''' - solange B erfüllt ist, wird die Schleife weiter ausgeführt.&lt;br /&gt;
&lt;br /&gt;
Da wir in dem Divisions-Algorithmus mit dem Typ '''int''' arbeiten, brauchen wir außerdem die für diesen Typ erlaubten Operationen, also die Axiome der ganzen Zahlen.&lt;br /&gt;
: '''A1:''' Kommutativität  x+y=y+x, x*y=y*x&lt;br /&gt;
: '''A2:''' Assoziativität  (x+y)+z=x+(y+z), (x*y)*z=x*(y*z)&lt;br /&gt;
: '''A3:''' Distributivität  x*(y+z)=x*y+x*z&lt;br /&gt;
: '''A4:''' Subtraktion (Inverses Element)  y&amp;amp;le;x &amp;amp;rArr; (x-y)+y=x&lt;br /&gt;
: '''A5:''' Neutrale Elemente  x+0=x, x*0=0, x*1=x&lt;br /&gt;
&lt;br /&gt;
==== Beweisen des Algorithmus ====&lt;br /&gt;
Vorbedingung: 0 &amp;lt; y,x&lt;br /&gt;
&lt;br /&gt;
Schleifeninvariante P (gleichzeitig Nachbedingung): x == y*q + r&lt;br /&gt;
  (1)  true &amp;amp;rArr; x==x+y*0                                          y*0==0 und x==x+0 folgen aus A5&lt;br /&gt;
  (2)  x==x+y*0              {r=x}                  x==r+y*0     D0: ersetze x durch r&lt;br /&gt;
  (3)  x==r+y*0              {q=0}                  x==r+y*q     D0: ersetze 0 durch q&lt;br /&gt;
  (4)  true                  {r=x}                  x==r+y*0     D1(b): kombiniere (1) und (2)&lt;br /&gt;
  (5)  true                  {r=x, q=0}             x==r+y*q     D2: kombiniere (4) und (3)&lt;br /&gt;
  (6)  x==r+y*q &amp;amp;and; y=r &amp;amp;rArr; x==(r-y)+y*(1+q)                       folgt aus A1...A5&lt;br /&gt;
  (7)  x==(r-y)+y*(1+q)      {r=r-y}                x==r+y*(1+q) D0: ersetze (r-y) durch r&lt;br /&gt;
  (8)  x==r+y*(1+q)          {q=q+1}                x==r+y*q     D0: ersetze (q+1) durch q&lt;br /&gt;
  (9)  x==(r-y)+y*(1+q)      {r=r-y, q=q+1}         x==r+y*q     D2: kombiniere (7) und (8)&lt;br /&gt;
  (10) x==r+y*q &amp;amp;and; y&amp;amp;le;r        {r=r-y, q=q+1}         x==r+y*q     D1(b): kombiniere (6) und (9)&lt;br /&gt;
  (11) x==r+y*q    {while y&amp;amp;le;r do (r=r-y, q=q+1)} x==r+y*q &amp;amp;and; &amp;amp;not;(y&amp;amp;le;r) D3: transformiere (10)&lt;br /&gt;
  (12) true        {r=x, q=0, &lt;br /&gt;
                    while y&amp;amp;le;r do (r=r-y, q=q+1)} x==r+y*q &amp;amp;and; &amp;amp;not;(y&amp;amp;le;r) D2: kombiniere (5) und (11)&lt;br /&gt;
&lt;br /&gt;
Im obigen Beweis ergibt sich sogar ''true'' als Vorbedingung (i.e. es gibt keine Vorbedingung). Dies liegt daran, dass Hoare in seinem Artikel durchweg von nicht-negativen Zahlen ausgeht. Diese Annahme wird beim Beweis von Zeile (6) benutzt.&lt;br /&gt;
&lt;br /&gt;
In der Praxis führt man solche Beweise natürlich nicht von Hand, sondern benutzt geeignete Programme, sogenannte [http://en.wikipedia.org/wiki/Automated_theorem_proving automatische Beweiser], die man allerding oft interaktiv steuern muss, weil der Beweis ohne diese Hilfe zu lange dauern würde.&lt;br /&gt;
&lt;br /&gt;
=== (Halb-)Automatisches Verfeinern ===&lt;br /&gt;
Dieses Verfahren ist beliebter, als das (halb-)automatische Beweisen. Die formale Spezifikation wird nach bestimmten, semantik-erhaltenden Transformationsregeln in ein ausführbares Programm umgewandelt. Mehr dazu z.B. in der [http://en.wikipedia.org/wiki/Program_refinement Wikipedia (Program refinement)]. Der Vorteil dieser Methode besteht darin, dass man die Transformationsregeln so definieren kann, dass nur das axiomatisierte Subset der Zielsprache benutzt wird. Dadurch wird der Korrektheitsbeweis stark vereinfacht.&lt;br /&gt;
&lt;br /&gt;
==Software-Tests==&lt;br /&gt;
&lt;br /&gt;
Dijkstra [http://de.wikipedia.org/wiki/Edsger_Wybe_Dijkstra] ließ einmal den Satz verlauten: &amp;quot;Tests können nie die Abwesenheit von Fehlern beweisen [Anwesenheit schon]&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Nach solch einer Aussage stellt sich die Frage, ob es sich überhaupt lohnt, mit dem Testverfahren die Korrektheit eines Algorithmus zu zeigen. Es erscheint einem doch plausibler sich auf die &amp;quot;formalen Methoden&amp;quot; zu berufen, mit dem Wissen, dass diese uns tatsächlich einen Beweis liefern können, ob nun H oder nicht H gilt. Zudem kommt noch erschwerend hinzu, dass es bei Tests bisher keine Theorie gibt, die sicherstellt, dass das Testprogramm einen vorhandenen Fehler zumindest mit hoher Wahrscheinlichkeit findet.&lt;br /&gt;
&lt;br /&gt;
Ein [http://de.wikipedia.org/wiki/Softwaretest Software-Test] versucht, ein Gegenbeispiel zur Hypothese H &amp;quot;der Algorithmus ist korrekt&amp;quot; zu finden. Dabei gibt es 4 Möglichkeiten:&lt;br /&gt;
 &lt;br /&gt;
   Algorithmus	   Testantwort	&lt;br /&gt;
      +	               +	        Algorithmus ist richtig, kein Gegenbeispiel gefunden&lt;br /&gt;
      -	               -	        Alg. ist falsch, und der Test erkennt den Fehler&lt;br /&gt;
      +	               -	        Bug im Test (Gegenbeispiel, obwohl Alg. richtig ist)&lt;br /&gt;
      -	               +	        Test hat versagt, da er den Fehler im Alg. nicht erkannt hat&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Wenn ein Gegenbeispiel zu H gefunden wird, kann man den Algorithmus (oder den Test) debuggen. Wird hingegen keines gefunden, nimmt man an, dass der Algorithmus korrekt ist. Man sieht, dass diese Annahme im Fall 4 nicht stimmt. Da Softwaretests jedoch in der Praxis sehr erfolgreich verwendet werden, ist dieser Fall offenbar nicht so häufig, dass man das Testen als Methode generell ablehnen müßte.&lt;br /&gt;
&lt;br /&gt;
=== Beispiel für das Testen: Freivalds Algorithmus ===&lt;br /&gt;
&lt;br /&gt;
Wir wollen die Wahrscheinlichkeit, dass ein Test einen vorhandenen Fehler übersieht, am Beispiel des [http://en.wikipedia.org/wiki/Freivald's_algorithm Algorithmus von Freivald] studieren. Es handelt sich dabei um einen randomisierten Algorithmus zum Testen der Matrixmultiplikation (siehe J. Hromkovič: ''&amp;quot;Randomisierte Algorithmen&amp;quot;'', Teubner 2004). Ziel dieses Algorithmuses ist es, die Hypothese H: &amp;quot;C ist das Produkt der Matrizen A und B&amp;quot; durch ein Gegenbeispiel zu widerlegen, wobei der Test einen anderen Algorithmus verwendet, um Vergleichsdaten zu gewinnen.&lt;br /&gt;
&lt;br /&gt;
  gegeben:&lt;br /&gt;
       Matrizen A, B, C  der Größe NxN &lt;br /&gt;
       Testhypothese H:  &amp;lt;tt&amp;gt;A*B == C&amp;lt;/tt&amp;gt;  Matrixmultiplikation (d.h. C wurde vorher durch C = mmul(A, B) berechnet, &lt;br /&gt;
                                            wobei mmul() der zu testende Multiplikationsalgorithmus ist).&lt;br /&gt;
 &lt;br /&gt;
  (1) Initialisierung      &lt;br /&gt;
       wähle Zufallsvektor der Länge N aus Nullen und Einsen: &amp;lt;math&amp;gt;\alpha \in \{0, 1\}^N &amp;lt;/math&amp;gt;  &lt;br /&gt;
  (2) Matrix-Vektor-Multiplikation (keine Matrix-Matrix-Multiplikation, denn die soll ja gerade verifiziert werden)&lt;br /&gt;
 &lt;br /&gt;
       &amp;lt;math&amp;gt;\left.\begin{array}{l}&lt;br /&gt;
                \beta = B*\alpha \\&lt;br /&gt;
                \gamma=A*\beta&lt;br /&gt;
                \end{array}\right\}A*(B*\alpha) == (A*B)*\alpha&lt;br /&gt;
       &amp;lt;/math&amp;gt; &lt;br /&gt;
 &lt;br /&gt;
       &amp;lt;math&amp;gt;\delta=C*\alpha&amp;lt;/math&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
  (3) Test der Korrektheit: falls &amp;lt;tt&amp;gt;A*B == C&amp;lt;/tt&amp;gt;, liefert der folgende Test stets &amp;lt;tt&amp;gt;true&amp;lt;/tt&amp;gt;:&lt;br /&gt;
 &lt;br /&gt;
       return   γ==δ&lt;br /&gt;
&lt;br /&gt;
Wir analysieren nun, mit welcher Wahrscheinlichkeit der Algorithmus den Fehler findet, wenn es denn einen gibt, d.h.&lt;br /&gt;
   &lt;br /&gt;
*Wahrscheinlichkeit '''p''', dass Freivalds Algorithmus den Fehler findet&amp;lt;br/&amp;gt;&lt;br /&gt;
oder&amp;lt;br/&amp;gt;&lt;br /&gt;
*Wahrscheinlichkeit '''q = 1 - p''', dass Freivalds Algorithmus den Fehler '''nicht''' findet.&lt;br /&gt;
&lt;br /&gt;
Wir schätzen diese Wahrscheinlichkeit ab für den einfachen Fall N=2. Wir definieren:&lt;br /&gt;
    &lt;br /&gt;
   &amp;lt;math&amp;gt;C=&lt;br /&gt;
  \begin{pmatrix} &lt;br /&gt;
    c_{11} &amp;amp; c_{12}  \\ &lt;br /&gt;
    c_{21} &amp;amp; c_{22}  &lt;br /&gt;
  \end{pmatrix},\qquad&lt;br /&gt;
\alpha=\begin{pmatrix}&lt;br /&gt;
    \alpha_1 \\&lt;br /&gt;
    \alpha_2 &lt;br /&gt;
     \end{pmatrix},\qquad&lt;br /&gt;
 \delta=\begin{pmatrix}&lt;br /&gt;
    \delta_1 \\&lt;br /&gt;
    \delta_2&lt;br /&gt;
 \end{pmatrix}&lt;br /&gt;
  = \begin{pmatrix}&lt;br /&gt;
    c_{11}\alpha_1 + c_{12}\alpha_2 \\&lt;br /&gt;
    c_{21}\alpha_1 + c_{22}\alpha_2&lt;br /&gt;
   \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Fallunterscheidung:'''&lt;br /&gt;
      &lt;br /&gt;
'''Fall 1:'''  C enthält genau 1 Fehler, z.B. &amp;lt;math&amp;gt;c_{11}&amp;lt;/math&amp;gt; hat falschen Wert&lt;br /&gt;
&lt;br /&gt;
:Der Fehler wird gefunden, wenn &amp;lt;math&amp;gt;\delta_1 \ne \gamma_1 \Leftrightarrow\alpha_1\ne 0&amp;lt;/math&amp;gt;. Da &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt; eine Zufallszahl aus &amp;lt;math&amp;gt;\{0,1\}&amp;lt;/math&amp;gt; ist, folgt daraus, dass '''p''' = '''q''' = &amp;lt;math&amp;gt;\frac{1}{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
       &lt;br /&gt;
'''Fall 2:'''  C enthält 2 Fehler&lt;br /&gt;
:(a)   in verschiedenen Zeilen und Spalten, z.B. &amp;lt;math&amp;gt;c_{11}&amp;lt;/math&amp;gt; und &amp;lt;math&amp;gt;c_{22}&amp;lt;/math&amp;gt;. Es gilt: Der Fehler in &amp;lt;math&amp;gt;c_{11}&amp;lt;/math&amp;gt; wird gefunden, wenn &amp;lt;math&amp;gt;\delta_1 \ne \gamma_1 \Leftrightarrow \alpha_1\ne 0&amp;lt;/math&amp;gt;. Unabhängig davon wird der Fehler in &amp;lt;math&amp;gt;c_{22}&amp;lt;/math&amp;gt; gefunden, wenn &amp;lt;math&amp;gt;\delta_2 \ne \gamma_2 \Leftrightarrow \alpha_2\ne 0&amp;lt;/math&amp;gt;. Da &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt; und &amp;lt;math&amp;gt;\alpha_2&amp;lt;/math&amp;gt; statistisch unabhängig sind, ist die Wahrscheinlichkeit für jedes dieser Ereignisse &amp;lt;math&amp;gt;q_1&amp;lt;/math&amp;gt; bzw. &amp;lt;math&amp;gt;q_2&amp;lt;/math&amp;gt; jeweils &amp;lt;math&amp;gt;\frac{1}{2}&amp;lt;/math&amp;gt;, und die Gesamtwahrscheinlichkeit '''q''', dass ''keiner'' der beiden Fehler gefunden wird, ist deren Produkt: '''q''' = &amp;lt;math&amp;gt;q_1*q_2 = \frac{1}{2}* \frac{1}{2} = \frac{1}{4}&amp;lt;/math&amp;gt;.        &lt;br /&gt;
&lt;br /&gt;
:(b) in verschiedenen Zeilen, gleichen Spalten, z.B. &amp;lt;math&amp;gt;c_{11}&amp;lt;/math&amp;gt; und &amp;lt;math&amp;gt;c_{21}&amp;lt;/math&amp;gt;. Es gilt: Der Fehler in &amp;lt;math&amp;gt;c_{11}&amp;lt;/math&amp;gt; wird gefunden, wenn &amp;lt;math&amp;gt;\delta_1 \ne \gamma_1 \Leftrightarrow \alpha_1\ne 0&amp;lt;/math&amp;gt;. Das gleiche gilt für den Fehler in &amp;lt;math&amp;gt;c_{21}&amp;lt;/math&amp;gt;. Die Wahrscheinlichkeit '''q''', dass ''keiner'' der beiden Fehler gefunden wird, ist demzufolge: '''q''' = &amp;lt;math&amp;gt;\frac{1}{2}&amp;lt;/math&amp;gt;.&lt;br /&gt;
               &lt;br /&gt;
:(c) in der gleichen Zeile, z.B. &amp;lt;math&amp;gt;c_{11}&amp;lt;/math&amp;gt; und &amp;lt;math&amp;gt;c_{12}&amp;lt;/math&amp;gt;. Es gilt: Der Fehler wird gefunden, wenn &amp;lt;math&amp;gt;\delta_1 \ne \gamma_1 \Leftrightarrow \alpha_1*c_{11}+\alpha_2*c_{12}\ne 0&amp;lt;/math&amp;gt;. Hier treten nun zwei ungünstige Fälle auf: &lt;br /&gt;
::1) Der Fehler wird u.a. dann nicht gefunden, wenn &amp;lt;math&amp;gt;\alpha_1 = \alpha_2=0&amp;lt;/math&amp;gt;. Die Wahrscheinlichkeit dafür ist  wieder '''q'''=&amp;lt;math&amp;gt;\frac{1}{4}&amp;lt;/math&amp;gt;&lt;br /&gt;
::2) &amp;lt;math&amp;gt;\alpha_1=\alpha_2=1&amp;lt;/math&amp;gt; (dies geschieht ebenfalls mit Wahrscheinlichkeit &amp;lt;math&amp;gt;\frac{1}{4}&amp;lt;/math&amp;gt;), aber die Werte &amp;lt;math&amp;gt;c_{11}&amp;lt;/math&amp;gt; und &amp;lt;math&amp;gt;c_{12}&amp;lt;/math&amp;gt; sind &amp;quot;zufälligerweise&amp;quot; so falsch, dass sich die Fehler gegenseitig aufheben. Die Wahrscheinlichkeit, dass beide Bedingungen gelten, ist auf jeden Fall '''q''' =  &amp;lt;math&amp;gt;\epsilon&amp;lt;\frac{1}{4}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Analog behandelt man die Fälle, dass C drei oder vier Fehler enthält. Fasst man die Fälle zusammen, ergibt sich, dass die Wahrscheinlichkeit, einen vorhandenen Fehler '''nicht''' zu entdecken, sicher kleiner als &amp;lt;math&amp;gt;\frac{1}{2}&amp;lt;/math&amp;gt; ist. Dies gilt auch allgemein:&lt;br /&gt;
&lt;br /&gt;
;Satz:&lt;br /&gt;
*Die Wahrscheinlichkeit, dass Freivalds Algorithmus einen vorhandenen Fehler '''nicht''' findet, ist '''q''' &amp;lt; &amp;lt;math&amp;gt;\frac{1}{2}&amp;lt;/math&amp;gt;. Wir haben diesen Satz oben für N=2 bewiesen, ein vollständiger Beweis findet sich in der [http://en.wikipedia.org/wiki/Freivald's_algorithm#Error_Analysis Wikipedia].&lt;br /&gt;
 &lt;br /&gt;
;Folgerung: &lt;br /&gt;
*Lässt man Freivalds Algorithmus mit verschiedenen &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt; k-mal laufen, gilt &amp;lt;math&amp;gt;q_k &amp;lt; 2^{-k}&amp;lt;/math&amp;gt; für die Wahrscheinlichkeit, dass '''keiner''' der k Durchläufe einen vorhandenen Fehler findet. Diese Wahrscheinlichkeit konvergiert sehr schnell gegen 0. Das heißt, der Algorithmus findet mit beliebig hoher Wahrscheinlichkeit ein Gegenbeispiel zu H (falls es eins gibt), wenn man ihn nur genügend oft mit jeweils anderen Zufallszahlen wiederholt. Daraus folgt, dass Testen ein effektives Fehlersuchverfahren sein kann -- die oben erwähnte Einschränkung von Dijktra trifft zwar zu, aber Tests, die mit so hoher Wahrscheinlichkeit funktionieren, sind für die Praxis meistens vollkommen ausreichend.&lt;br /&gt;
&lt;br /&gt;
=== Vergleich formaler Korrektheitsbeweis und Testen ===&lt;br /&gt;
&lt;br /&gt;
Nachdem nun die formalen Methoden sowie der Software-Test vorgesellt worden sind, ist nun die Frage, welcher der beiden Vorgänge der bessere ist, aufzugreifen. Allgemein gilt:&lt;br /&gt;
&lt;br /&gt;
;randomisierte Algorithmen&lt;br /&gt;
               &lt;br /&gt;
*sind schnell und einfach:&lt;br /&gt;
#da die Operationen einfach sind und wenig Zeit kosten&lt;br /&gt;
#des öfteren eine Auswahl vorgenommen wird ohne die Gesamtmenge näher zu betrachten&lt;br /&gt;
#die Auswahl selbst aufgrund einfacher Kriterien (bspw. zufällige Auswahl) erfolgt&lt;br /&gt;
*können Lösungen approximieren und liefern gute approximative Lösungen&lt;br /&gt;
&lt;br /&gt;
;formaler Korrektheitsbeweis mit deterministischen Algorithmen (siehe auch [http://de.wikipedia.org/wiki/Determinismus_(Algorithmus)])&lt;br /&gt;
  &lt;br /&gt;
*bei jedem Aufruf des Beweisers werden immer die selben Schritte durchlaufen&lt;br /&gt;
*keine Zufallswerte&lt;br /&gt;
*komplexer Aufbau&lt;br /&gt;
*oft sehr lange Laufzeit, z.B. mehrere Tage oder gar Monate&lt;br /&gt;
&lt;br /&gt;
Für die formalen Methoden spricht, dass man mit ihnen im Prinzip beweisen kann, dass H nun entweder tatsächlich falsch oder richtig ist. Die formalen Beweise bei realen Problemen sind allerdings so kompliziert, dass sie ebenfalls mit Computerhilfe erbracht werden müssen. Dadurch liegt auch hier keine 100%-ige Korrektheitsgarantie vor: Auch formale Methoden können zum falschen Ergebnis kommen, z.B. durch Hardwarefehler, Compilerbugs, oder unvorhergesehenes Umkippen von Bits (z.B. durch kosmische Strahlung -- diese Gefahr ist im Weltall sehr ernst zu nehmen). Die Möglichkeit von Hardwarefehlern wirkt sich auf die formalen Methoden wesentlich stärker aus, weil diese typischerweise wesentlich längere Laufzeiten haben als entsprechende Testalgorithmen. Es kann deshalb durchaus vorkommen, dass Tests eine höhere Erfolgswahrscheinlichkeit haben als ein formaler Beweis, wie die folgende Beispielrechnung zeigt. Wir nehmen an, dass die Hardware eine &amp;quot;Halbwertszeit&amp;quot; von 50 Millionen Sekunden hat, d.h. ein Hardwarefehler tritt im Durchschnitt etwa alle 20 Monate auf. Dann ist die Wahrscheinlichkeit, dass ein deterministischer Algorithmus '''nicht''' zum Ergebnis (oder zum falschen Ergebnis) kommt:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;math&amp;gt;q_{\mathrm{Beweis}} \approx 0.001&amp;lt;/math&amp;gt;, falls der Beweisalgorithmus 1 Tag benötigt,&lt;br /&gt;
* &amp;lt;math&amp;gt;q_{\mathrm{Beweis}} \approx 0.01&amp;lt;/math&amp;gt;, falls der Beweisalgorithmus 1 Woche benötigt,&lt;br /&gt;
* &amp;lt;math&amp;gt;q_{\mathrm{Beweis}} \approx 0.035&amp;lt;/math&amp;gt;, falls der Beweisalgorithmus 1 Monat benötigt.&lt;br /&gt;
&lt;br /&gt;
Zum Vergleich nehmen wir an, dass der entsprechende Softwaretest einmal pro Sekunde ausgeführt werden kann, und dass jeder Durchlauf den Fehler mit einer Wahrscheinlichkeit von &amp;lt;math&amp;gt;\frac{1}{2}&amp;lt;/math&amp;gt; '''nicht''' findet. Unter gleichzeitiger Berücksichtigung der Wahrscheinlichkeit von Hardwarefehlern gilt dann&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;math&amp;gt;q_{\mathrm{Test}} \approx 0.5&amp;lt;/math&amp;gt;, falls der Test 1-mal wiederholt wird,&lt;br /&gt;
* &amp;lt;math&amp;gt;q_{\mathrm{Test}} \approx 0.001&amp;lt;/math&amp;gt;, falls der Test 10-mal wiederholt wird,&lt;br /&gt;
* &amp;lt;math&amp;gt;q_{\mathrm{Test}} \approx 10^{-6}&amp;lt;/math&amp;gt;, falls der Test 100-mal wiederholt wird.&lt;br /&gt;
&lt;br /&gt;
Mit anderen Worten: hier ist das Testen vorzuziehen, weil es unter realistischen Bedingungen eine höhere Erfolgswahrscheinlichkeit hat als der formale Beweis. Leider gibt es bisher keine Theorie, mit deren Hilfe man für ein gegebenes Problem systematisch Tests konstruieren kann, deren Misserfolgswahrscheinlichkeit bei wiederholter Anwendung garantiert so schnell gegen Null konvergiert wie die des Freivalds Algorithmus. Dies ist ein offenes Problem der Informatik.&lt;br /&gt;
&lt;br /&gt;
==Anwendung des Softwaretestverfahren==&lt;br /&gt;
===Beispiel an Python-Code===&lt;br /&gt;
&lt;br /&gt;
Man betrachte die Aufgabe, aus einer Zahl x die Wurzel zu ziehen. Dies kann man erreichen, indem man mit Hilfe des Newtonschen Iterationsverfahrens eine Nullstelle des Polynoms &lt;br /&gt;
:&amp;lt;math&amp;gt;f(y) = x - y^2 = 0&amp;lt;/math&amp;gt; &lt;br /&gt;
sucht. Ist eine Näherungslösung &amp;lt;math&amp;gt;y^{(t)}&amp;lt;/math&amp;gt; bekannt, erhält man eine bessere Näherung durch&lt;br /&gt;
:&amp;lt;math&amp;gt;y^{(t+1)} = y^{(t)} - \frac{f(y^{(t)})}{f'(y^{(t)})}&amp;lt;/math&amp;gt;.&lt;br /&gt;
Mit &amp;lt;math&amp;gt;f\,'(y) = -2y&amp;lt;/math&amp;gt; wird das zu&lt;br /&gt;
:&amp;lt;math&amp;gt;y^{(t+1)} = y^{(t)} + \frac{x-(y^{(t)})^2}{2y^{(t)}}=\frac{y^{(t)}+x/y^{(t)}}{2}&amp;lt;/math&amp;gt;. &lt;br /&gt;
Im Spezialfall des Wurzelziehens war diese Newton-Iteration übrigens bereits im Altertum als [http://en.wikipedia.org/wiki/Babylonian_method#Babylonian_method Babylonische Methode] bekannt. Man kann dieselbe durch das folgende (allerding noch nicht korrekte) Pythonprogramm realisieren:&lt;br /&gt;
&lt;br /&gt;
           1   def sqrt(x):&lt;br /&gt;
           2       if (x&amp;lt;0):&lt;br /&gt;
           3           raise ValueError(&amp;quot;sqrt of negative number&amp;quot;)&lt;br /&gt;
           4       y = x / 2&lt;br /&gt;
           5       while y*y != x:&lt;br /&gt;
           6           y =(y + x/y) / 2&lt;br /&gt;
           7       return y:&lt;br /&gt;
&lt;br /&gt;
Für den oben aufgeführten Pythoncode können Tests mit Hilfe des Python-Moduls &amp;quot;[http://docs.python.org/lib/module-unittest.html unittest]&amp;quot; geschrieben werden (siehe auch Übungsaufgaben). Wir erklären hier die wichtigsten Befehle aus diesem Modul. Wir implementieren eine Testfunktionen (diese muss, wie im Python-Handbuch beschrieben, Methode einer Testklasse sein).&lt;br /&gt;
&lt;br /&gt;
   class SqrtTest(unittest.TestCase):&lt;br /&gt;
     def testsqrt(self): &lt;br /&gt;
         ...&lt;br /&gt;
&lt;br /&gt;
Zunächst muss man prüfen, ob die Vorbedingung korrekt getestet wird, d.h. ob bei einer negativen Zahl x eine Exception ausgelöst wird; dafür benötigt man &lt;br /&gt;
&lt;br /&gt;
         self.assertRaises(ValueError, sqrt, -1) &lt;br /&gt;
Sollte keine Exception vom Type &amp;lt;tt&amp;gt;ValueError&amp;lt;/tt&amp;gt; ausgelöst werden, dann würde der Test hier einen Fehler signalisieren. Dieser Test funktioniert aber.&lt;br /&gt;
&lt;br /&gt;
Weiter testen wir einige Beispiele, deren Wurzel wir kennen:&lt;br /&gt;
&lt;br /&gt;
         self.assertEqual(sqrt(9),3) &lt;br /&gt;
Wäre hier das Ergebnis ungleich 3, würde ebenfalls ein Fehler signalisiert, aber es funktioniert in unserem Falle. Der Test&lt;br /&gt;
&lt;br /&gt;
         self.assertEqual(sqrt(1),1)&lt;br /&gt;
schlägt jedoch mit &amp;lt;tt&amp;gt;ZeroDivisionError&amp;lt;/tt&amp;gt; fehl! Wir sehen, dass in Zeile 4 eine Ganzzahldivision durchgeführt wird, deren Ergebnis stets abgerundet wird, was hier zu &amp;lt;tt&amp;gt;y = 0&amp;lt;/tt&amp;gt; und damit zum Fehler in Zeile 6 führt. Wieso hat dann aber der erste Test &amp;lt;tt&amp;gt;sqrt(9) == 3&amp;lt;/tt&amp;gt; funktioniert? Hier gilt &amp;lt;tt&amp;gt;x / 2 == 4&amp;lt;/tt&amp;gt; und &amp;lt;tt&amp;gt;x / y == 2&amp;lt;/tt&amp;gt; (jeweils nach Abrunden), und der Mittelwert der beiden Schätzungen ist gerade &amp;lt;tt&amp;gt;y == 3&amp;lt;/tt&amp;gt;, also zufällig das richtige Ergebnis. Allgemein sehen wir jedoch, dass es nicht korrekt ist, mit ganzen Zahlen zu rechnen. Wir müssen also den Input zunächst in einen Gleitkommawert umwandeln:&lt;br /&gt;
&lt;br /&gt;
           1   def sqrt(x):&lt;br /&gt;
           1a      x = float(x)&lt;br /&gt;
           2       if (x&amp;lt;0):&lt;br /&gt;
           3           raise ValueError(&amp;quot;sqrt of negative number&amp;quot;)&lt;br /&gt;
           4       y = x / 2&lt;br /&gt;
           5       while y*y != x:&lt;br /&gt;
           6           y =(y + x/y) / 2&lt;br /&gt;
           7       return y:&lt;br /&gt;
&lt;br /&gt;
Jetzt funktionieren die vorhandenen Tests, aber bei anderen Zahlen (z.B. &amp;lt;tt&amp;gt;x = 1.21&amp;lt;/tt&amp;gt;) läuft das Programm in eine Endlosschleife. Dies liegt daran, dass durch die beschränkte Genauigkeit der Gleitkomma-Darstellung selten exakte Gleichheit in der &amp;lt;tt&amp;gt;while&amp;lt;/tt&amp;gt;-Bedingung erreicht wird. Man darf nicht auf Gleichheit prüfen, sondern muss den relativen Fehler beschränken:&lt;br /&gt;
&lt;br /&gt;
           1   def sqrt(x):&lt;br /&gt;
           1a      x = float(x)&lt;br /&gt;
           2       if (x&amp;lt;0):&lt;br /&gt;
           3           raise ValueError(&amp;quot;sqrt of negative number&amp;quot;)&lt;br /&gt;
           4       y = x / 2&lt;br /&gt;
           5       while abs(1.0 - x / y**2) &amp;gt; 1e-15:  # check for relative difference&lt;br /&gt;
           6           y =(y + x/y) / 2&lt;br /&gt;
           7       return y:&lt;br /&gt;
&lt;br /&gt;
Jetzt terminiert das Programm, aber der Test&lt;br /&gt;
&lt;br /&gt;
        self.assertEqual(sqrt(1.21)**2, 1.21)  # schlägt fehl&lt;br /&gt;
&lt;br /&gt;
schlägt wegen der beschränkten Genauigkeit der Gleitkommadarstellung fehl. Man umgeht dieses Problem, indem man im Tests selbst nur nähreungsweise Gleichheit fordert, z.B. auf 15 Dezimalstellen genau (bei 16 Dezimalen würde es nicht mehr funktionieren):&lt;br /&gt;
&lt;br /&gt;
        self.assertAlmostEqual(sqrt(1.21)**2, 1.21, 15)&lt;br /&gt;
&lt;br /&gt;
Wenden wir jetzt das ''Prinzip der Condition Coverage'' an (siehe unten), sehen wir, dass die &amp;lt;tt&amp;gt;while&amp;lt;/tt&amp;gt;-Bedingung bei allen bisherigen Tests zunächst mindestens einmal &amp;lt;tt&amp;gt;true&amp;lt;/tt&amp;gt; gewesen ist. Ein weiterer sinnvoller Tests ist deshalb einer, der diese Bedingung sofort &amp;lt;tt&amp;gt;false&amp;lt;/tt&amp;gt; macht. Dies trifft z.B. bei &amp;lt;tt&amp;gt;x == 4&amp;lt;/tt&amp;gt; zu, weil &amp;lt;tt&amp;gt;y = x / 2&amp;lt;/tt&amp;gt; hier gerade die korrekte Wurzel liefert. Wir fügen deshalb den Test&lt;br /&gt;
&lt;br /&gt;
         self.assertEqual(sqrt(4), 2) &lt;br /&gt;
&lt;br /&gt;
hinzu, der erfolgreich verläuft. Das ''Prinzip der Domänen-Zerlegung'' (siehe unten) führt uns weiter dazu, die Wurzel aus Null als sinnvollen Test zu betrachten, weil die Null am Rand des erlaubten Wertebereichs liegt. Der Test&lt;br /&gt;
&lt;br /&gt;
        self.assertEqual(sqrt(0), 0)  # schlägt fehl&lt;br /&gt;
&lt;br /&gt;
schlägt in der Tat mit einem &amp;lt;tt&amp;gt;ZeroDivisionError&amp;lt;/tt&amp;gt; fehl: In der Abfrage der &amp;lt;tt&amp;gt;while&amp;lt;/tt&amp;gt;-Bedingung wird jetzt durch &amp;lt;tt&amp;gt;y == 0&amp;lt;/tt&amp;gt; geteilt. Wir können diesen Fehler beheben, indem wir die Division aus der Bedingung eliminieren:&lt;br /&gt;
&lt;br /&gt;
           1   def sqrt(x):&lt;br /&gt;
           1a      x = float(x)&lt;br /&gt;
           2       if (x&amp;lt;0):&lt;br /&gt;
           3           raise ValueError(&amp;quot;sqrt of negative number&amp;quot;)&lt;br /&gt;
           4       y = x / 2&lt;br /&gt;
           5       while abs(y**2 - x) &amp;gt; 1e-15*x:  # check for relative difference without division&lt;br /&gt;
           6           y =(y + x/y) / 2&lt;br /&gt;
           7       return y:&lt;br /&gt;
&lt;br /&gt;
Damit ist auch dieses Problem behoben. Wir sehen also, wie das systematische Testen uns dabei hilft, Fehler im Programm zu finden und zu eliminieren.&lt;br /&gt;
&lt;br /&gt;
===Definition guter Tests===&lt;br /&gt;
&lt;br /&gt;
Wir haben gezeigt, dass Testen eine effektive Methode ist, um Fehler in Algorithmen zu finden. Allerdings gilt das nur, wenn Tests und Testdaten geschickt gewählt werden. Wir zeigen bewährte Methoden dafür. &lt;br /&gt;
&lt;br /&gt;
====Generieren von Referenzdaten====&lt;br /&gt;
&lt;br /&gt;
Wie immer man die Tests definiert hat, muss man am Ende die Ausgabe des Algorithmus mit dem korrekten Ergebnis vergleichen. Man bezeichnet ein bekanntes korrektes Ergebnis als ''Referenz-Ergebnis''. Dieses muss man aber erst einmal kennen, was sich mitunter als schwierig erweist. Folgende Verfahren haben sich als zweckmäßig erwiesen:&lt;br /&gt;
* Bei bestimmten Eingaben ist das Ergebnis für den Menschen einfach zu bestimmen, für den Algorithmus ist diese Eingabe aber ebenso schwierig wie jede andere. Dies gilt zum Beispiel für die Quadratzahlen im obigen Beispiel: der Algorithmus kennt keine Quadratzahlen und behandelt sie wie jede andere reelle Zahl. Deshalb eignen sich die Quadratzahlen zum Testen. Auch beim Sortieren kleiner Listen kann die korrekte Sortierung leicht bestimmt und als Referenz-Ergebnis abgespeichert werden. Der Test vergleicht dann einfach die Ausgabe des Sortieralgorithmus mit dem Referenz-Ergebnis.&lt;br /&gt;
* Oft kann man das korrekte Ergenis mit einem alternativen Verfahren berechnen. Dies gilt insbesondere, wenn man einen effizienten, aber komplizierten Algorithmus testen will. Dann berechnet man die Referenz-Ergebnisse mit einem langsamen, aber einfachen Verfahren. Dies ist möglich, weil man die Referenz-Ergebnisse ja abspeichern kann und der langsame Algorithmus daher nur wenige Male benutzt werden muss. Beispielsweise kann man einen komplizierten Sortieralgorithmus (Quicksort) mit Hilfe von selection sort testen.&lt;br /&gt;
* In vielen Fällen steht ein alternatives Programm zur Verfügung, z.B. eine ältere Version des zu testenden Programms, oder ein kommerzielles Programm (bzw. eine Demoversion), das dasselbe Problem löst, aber im aktuellen Kontext nicht verwendet werden kann (weil es z.B. zu teuer ist, oder nur auf einem Mac läuft). Diese Methode bietet sich auch an, wenn man einen Algorithmus aus einer Programmiersprache in eine andere portieren muss. &lt;br /&gt;
* Manchmal kann das korrekte Ergebnis nicht direkt angegeben werden, aber man kennt bestimmte Eigenschaften. Beim Sortieren kann man z.B. testen, dass kein Element des sortierten Arrays größer ist als das darauffolgende. Man testes also die Nachbedingungen. Eine abgeschwächte Versionen dieser Methode wird für randomisierte Algorithmen verwendet: Ist die Wahrscheinlichkeitsverteilung der Testeingaben bekannt, kann man die Wahrscheinlichkeitsverteilung der Ergebnisse, oder zumindest wichtige Eigenschaften wie z.B. den Mittelwert, mathematisch vorhersagen. Der Test ermittelt dann, ob die Ausgaben über viele Durchläufe des Algorithmus diese statistischen Eigenschaften aufweisen.&lt;br /&gt;
&lt;br /&gt;
====Arten von Tests====&lt;br /&gt;
&lt;br /&gt;
Man unterscheidet 3 grundlegende Arten von Tests:&lt;br /&gt;
&lt;br /&gt;
;Black-box Tests [http://en.wikipedia.org/wiki/Black_box_testing]: Hier ist dem Tester nur die Spezifikation, aber nicht die Implementation des Algorithmus bekannt. Alle Tests sowie die Eingaben und Referenz-Ergebnisse müssen aus der Spezifikation abgeleitet werden. Die automatisierte Generierung guter Tests aus der Spezifikation ist ein aktives Forschungsgebiet.&lt;br /&gt;
;Gray-box Tests (auch Glass-box Tests) [http://www.cse.fau.edu/~maria/COURSES/CEN4010-SE/C13/glass.htm]: Hier kennt der Tester auch die Implementation und kann dadurch Tests entwerfen, die für diese spezielle Implementation besonders aussagekräftig sind. Es besteht allerdings die Gefahr, dass der Tester nicht mehr unvoreingenommen an das Testproblem herangeht, und Zustände, die seiner Meinung nach gar nicht vorkommen können, auch nicht testet (erst später stellt sich heraus, dass diese Zustände doch vorkommen).&lt;br /&gt;
;White-box Tests [http://en.wikipedia.org/wiki/White_box_testing]: Hier kann der Tester die Implementation sogar in geeigneter Weise verändern, z.B. &lt;br /&gt;
:* explizite Tests für Vor- und Nachbedingungen (&amp;quot;Assertions&amp;quot;) einbauen. Dies bietet sich insbesondere in der alpha- und beta-Testphase eines Programms an, um Fehler schnell zu lokalisieren. Auch die unter Windows bekannte Dialogbox &amp;quot;Diesen Fehler bitte auch an Microsoft melden&amp;quot; wird durch solche eingebauten Assertions ausgelöst, wenn das Programm in einen illegalen Zustand geraten ist und abgebrochen werden muss.&lt;br /&gt;
:* zusätzlichen Code einbauen, der feststellt, ob alle Teile des Programms auch tatsächlich getestet wurden (&amp;quot;[http://blogs.msdn.com/phuene/archive/2007/05/03/code-coverage-instrumentation.aspx code coverage instrumentation]&amp;quot;). Dieser Code gibt nach dem Testen z.B. aus, welche Programmzeilen von keinem existierenden Test aufgerufen worden sind. Wenn der ausgeführte Code sehr stark von den Daten abhängt (z.B. bei interaktiven Programmen), kann es sehr schwierig sein, die ''coverage'' auf andere Weise festzustellen.&lt;br /&gt;
:* absichtlich Bugs einbauen (die automatisch wieder abgeschaltet werden, wenn das Testen vorbei ist). Durch diese &amp;quot;[http://en.wikipedia.org/wiki/Fault_injection fault injection]&amp;quot; kann man herausfinden, ob die Tests mächtig genug sind, vorhandene Bugs zu finden.&lt;br /&gt;
&lt;br /&gt;
====Prinzipien für die Generierung von Testdaten====&lt;br /&gt;
&lt;br /&gt;
;Prinzip der Regressionstests (&amp;quot;[http://en.wikipedia.org/wiki/Regression_testing Regression testing]&amp;quot;): Häufig werden Tests während der Programmentwicklung verwendet, um einen Algorithmus zu debuggen. Sobald der Algorithmus aber funktioniert werden die Tests gelöscht, denn sie werden ja jetzt nicht mehr gebraucht. Dies ist ein schwerwiegender ''Fehler'': Jedes erfolgreiche Programm muss früher oder später weiterentwickelt werden (zumindest die Anpassung an eine neue Betriebssystemversion ist ab und zu notwendig). Jede Änderung birgt aber die Gefahr, dass sich neue Bugs in bisher funktionierenden Code einschleichen. Man sollte deshalb alle Tests aufheben und in einer ''test suite'' sammeln. Durch diese &amp;quot;regression tests&amp;quot; kann man nach jeder Änderung feststellen, ob die alte Funktionalität noch intakt ist, und gegebenenfalls die letzte Änderung einfach rückgängig machen. Tut man dies nicht, kann die Gefahr von unbeabsichtigten destruktiven Änderungen so groß werden, dass das Programm gar nicht mehr weiterentwickelt werden kann. Dies wird drastisch durch den bekannten Spruch &amp;quot;never change a running program&amp;quot; ausgedrückt.&lt;br /&gt;
&lt;br /&gt;
;Prinzip der äquvalenten Eingaben (Domain Partitioning oder Equivalence Partitioning) [http://en.wikipedia.org/wiki/Equivalence_partitioning]: Für ähnliche Eingaben verhält sich ein Algorithmus normalerweise ähnlich, und es hat keinen Sinn, alle diese Eingaben zu testen. Statt dessen teilt (partitioniert) man die Eingabedomäne in Äquivalenzklassen, die vom Algorithmus im wesentlichen gleich behandelt werden. Im obigen Beispiel der Wurzelberechnung ergeben sich zwei Klassen aus der Spezifikation: die negativen Zahlen (für die die Wurzel undefiniert ist und deshalb ein Fehler signalisiert werden muss) und die nicht-negativen Zahlen. Wenn man auch den Quellcode kennt (gray-box testing), kann man die Eingaben oft feiner unterteilen. Z.B. werden häufig unterschiedliche Algorithmen für kleine und für große Eingaben benutzt. Viele Quicksort-Implementationen verwenden beispielsweise für Arrays mit höchstens vier Elementen ein explizites Sortierverfahren, für Arrays der Länge 5 bis 25 selection sort, und erst für größere Arrays das eigentliche Quicksort. Aus der Einteilung der Eingabedomäne ergeben sich zwei wichtige Regeln für die Wahl der Testdaten:&lt;br /&gt;
:* Aus jeder Äquivelenzklasse wählt man mindestens einen typischen Vertreter, um das normale Verhalten des Algorithmus in jedem Fall zu testen.&lt;br /&gt;
:* Aus jeder Äquivelenzklasse wählt man Randwerte, weil gerade bei diesen Werten am häufigsten Fehler gemacht werden. Im obigen Wurzelbeispiel ist der Randwert die Null, die in der Tat in einer Version des Algorithmus zu einem &amp;lt;TT&amp;gt;ZeroDivisionError&amp;lt;/tt&amp;gt; geführt hat. Andere typische Randfehler sind, dass Randelemente dem falschen Algorithmenzweig zugeordnet werden (z.B. wenn bei unserem Wurzelbeispiel die Abfrage am Anfang &amp;lt;tt&amp;gt;if x &amp;lt;= 0:&amp;lt;/tt&amp;gt; statt &amp;lt;tt&amp;gt;if x &amp;lt; 0:&amp;lt;/tt&amp;gt; gewesen wäre), dass Schleifen um einen Index zu spät beginnen oder zu früh abbrechen (&amp;quot;[http://en.wikipedia.org/wiki/Off-by-one_error Off-by-one errors]&amp;quot;), oder dass ein seltener Randfall gar nicht implementiert ist und einfach zum Absturz führt.&lt;br /&gt;
&lt;br /&gt;
;Prinzip, den Fehler zu reproduzieren (Failure Reproduction): Wenn ein Bug gemeldet wird, welches die Tests bisher übersehen haben, fügt man einen Test hinzu, der dieses Bug findet. Im Zusammenhang mit regression tests ist damit sichergestellt, dass dasselbe Bug nicht noch einmal auftreten kann.&lt;br /&gt;
&lt;br /&gt;
;Prinzip der Code Coverage [http://en.wikipedia.org/wiki/Code_coverage]: Hier stellt man sicher, dass tatsächlich der gesamte Code (oder ein vorher festgelegter hoher Prozentsatz) gestestet wurde. Gerade bei komplizierten interaktiven Programmen ist diese &amp;quot;code coverage&amp;quot; mitunter nicht leicht zu erreichen, weil manche Programmteile nur bei sehr seltenen oder obskuren Eingaben ausgeführt werden. Eine minimale code coverage erreicht man allerdings bereits, wenn man in einem black-box-Test die Testdaten nach dem Prinzip der äquivalenten Eingaben auswählt, weil dann aus jeder Äquivalenzklasse mindestens ein Vertreter getestet wird. Im Allgemeinen muss man aber den Quellcode zumindest kennen (gray-box-Test), um geeignete Testdaten für code coverage zu identifizieren. Code coverage kann in verschiednen Graden angestrebt werden&lt;br /&gt;
:* Function coverage: Jede Funktion eines Programms sollte mindestens einmal aufgerufen werden.&lt;br /&gt;
:* Statement coverage: Jedes Statement (d.h. im wesentlichen jede Programmzeile) sollte mindestens einmal ausgeführt werden. Im obigen Wurzelbeispiel erfordert dies, dass z.B. mindestens einmal eine negative Zahl getestet wird, um die Exception zu prüfen.&lt;br /&gt;
:* Condition coverage: Jede Bedingung (explizit in &amp;lt;tt&amp;gt;if&amp;lt;/tt&amp;gt;-Bedingungen, implizit in den Abbruchbedingungen von &amp;lt;tt&amp;gt;for&amp;lt;/tt&amp;gt;- und &amp;lt;tt&amp;gt;while&amp;lt;/tt&amp;gt;-Schleifen) sollte mindestens einmal mit dem Ergebnis &amp;lt;tt&amp;gt;True&amp;lt;/tt&amp;gt; und einmal mit dem Ergebnis &amp;lt;tt&amp;gt;False&amp;lt;/tt&amp;gt; durchlaufen werden. Im Wurzelbeispiel haben wir die Eingabe &amp;lt;tt&amp;gt;x = 4&amp;lt;/tt&amp;gt; gewählt, damit die &amp;lt;tt&amp;gt;while&amp;lt;/tt&amp;gt;-Schleife auch einmal beim ersten Aufruf sofort &amp;lt;tt&amp;gt;False&amp;lt;/tt&amp;gt; liefert.&lt;br /&gt;
:* Path coverage: Jeder Programmpfad (d.h. jede Kombination von Wahrheitswerten bei allen Bedingungen) sollte einmal ausgeführt werden. Dies ist im Allgemeinen unerreichbar, weil es unendlich viele, oder zumindest zu viele verschiedene Pfade gibt.&lt;br /&gt;
:Die Qualität der Tests steigt, wenn eine hohe Coverage (am besten 100%) erreicht wird, und/oder man eine mächtigere Art von Coverage fordert.&lt;br /&gt;
&lt;br /&gt;
;Prinzip der erschöpfenden Tests: Wenn ein Algorithmus nur wenige mögliche Eingaben hat, kann man sämtliche Eingaben testen. Bei sehr wichtigen Algorithmen kann das auch dann noch sinnvoll sein, wenn es relativ viele mögliche Eingaben gibt. In den meisten Fällen ist es jedoch zu aufwändig.&lt;br /&gt;
&lt;br /&gt;
;Prinzip der vollständigen Paarung (Pair-wise coverage) [http://citeseer.ist.psu.edu/78354.html]: Wenn ein Algorithmus N Eingabeparameter hat, und jeder Parameter hat K&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; mögliche Werte, müssen bei der erschöpfenden Suche K&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;*...*K&amp;lt;sub&amp;gt;N&amp;lt;/sub&amp;gt; Kombinationen getestet werden. Beschränkt man sich in jedem Parameter auf typische Werte und Randwerte jeder Äquivalenzklasse, kann man K&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; zwar drastisch reduzieren, aber das Produkt K&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;*...*K&amp;lt;sub&amp;gt;N&amp;lt;/sub&amp;gt; wird immer noch sehr groß (bei 4 Parametern und nur 3 möglichen Werten pro Parameter hat man bereits 3&amp;lt;sup&amp;gt;4&amp;lt;/sup&amp;gt;=81 mögliche Kombinationen). Sei v&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt; der j-te Wert des Parameters i. Anstatt zu versuchen, alle Kombinationen zu testen, kann man fordern, dass zumindest alle möglichen Paare v&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt; und v&amp;lt;sub&amp;gt;mj&amp;lt;/sub&amp;gt; (i&amp;amp;ne;m) in mindestens einem Test vorkommen. Gibt es nur zwei Parameter, gewinnt man durch diese Einschränkung natürlich nichts, denn man muss mindestens K&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;*K&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; Tests durchführen. Hat man jedoch 3 Parameter, kann man mit weniger Tests auskommen als zuvor, da jeder Test bis zu drei verschiedene Paarungen abdecken kann (eine für den ersten und zweiten Parameter, eine für den ersten und dritten, eine für den zweiten und dritten). Bei vier Parametern werden sogar sechs Paarungen pro Test abgearbeitet usw. Die Theorie des &amp;quot;experimental design&amp;quot; beschreibt nun, wie man systematisch alle möglichen Paarungen mit möglichst wenigen Tests erzeugt. Es stellt sich heraus, dass man alle Paarungen von 3, 4 oder mehr Parametern oft mit genauso vielen Tests erzeugen kann wie bei 2 Parametern nötig wären. Dazu verwendet man die Methode der [http://en.wikipedia.org/wiki/Latin_square Latin Squares].  Wir beschreiben diese Methode für den einfachen Fall von 3 möglichen Werten pro Parameter.&lt;br /&gt;
&lt;br /&gt;
:Ein Latin Square der Größe 3 ist eine 3x3 Matrix, deren Einträge die Zahlen 1...3 sind, und zwar so, dass jede Zahl genau einmal in jeder Zeile und Spalte vorkommt (ähnlich wie beim Sudoku). Eine mögliche Matrix ist z.B.&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;P=\begin{pmatrix}1 &amp;amp; 2 &amp;amp; 3 \\&lt;br /&gt;
                      2 &amp;amp; 3 &amp;amp; 1 \\&lt;br /&gt;
                      3 &amp;amp; 1 &amp;amp; 2\end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
:Man bildet jetzt 9 Kombinationen der Zahlen 1...3, indem man zeilenweise durch die Matrix P geht, und den Zeilenindex (die Nummer der aktuellen Zeile) als erste Zahl, den Spaltenindex als zweite Zahl, und den Eintrag an der aktuallen Position als dritte Zahl verwendet. Man erhält&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;0&amp;quot; cellpadding=&amp;quot;7&amp;quot; align=&amp;quot;center&amp;quot;&lt;br /&gt;
|-align=&amp;quot;center&amp;quot; &lt;br /&gt;
|&lt;br /&gt;
! Komb. 1&lt;br /&gt;
! Komb. 2&lt;br /&gt;
! Komb. 3&lt;br /&gt;
! Komb. 4&lt;br /&gt;
! Komb. 5&lt;br /&gt;
! Komb. 6&lt;br /&gt;
! Komb. 7&lt;br /&gt;
! Komb. 8&lt;br /&gt;
! Komb. 9&lt;br /&gt;
|-&lt;br /&gt;
!Zahl 1 (Zeilenindex)&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|-&lt;br /&gt;
! Zahl 2 (Spaltenindex)&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|-&lt;br /&gt;
! Zahl 3 (aktueller Matrixeintrag von P)&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
:Diese Tabelle bestimmt, welcher Wert in jedem Test für jeden Parameter verwendet wird. Z.B. wird der erste Test mit v&amp;lt;sub&amp;gt;11&amp;lt;/sub&amp;gt; (erster Wert des ersten Parameters), v&amp;lt;sub&amp;gt;21&amp;lt;/sub&amp;gt; (erster Wert des zweiten Parameters), v&amp;lt;sub&amp;gt;31&amp;lt;/sub&amp;gt; (erster Wert des dritten Parameters) aufgerufen&lt;br /&gt;
       assertEqual( foo(v11, v21, v31), foo_reference1)&lt;br /&gt;
(reference1 ist das korrekte Referenz-Ergebnis für diese Prameterbelegung). Der letzte Test hat die Parameter v&amp;lt;sub&amp;gt;13&amp;lt;/sub&amp;gt;, v&amp;lt;sub&amp;gt;23&amp;lt;/sub&amp;gt;, v&amp;lt;sub&amp;gt;32&amp;lt;/sub&amp;gt;&lt;br /&gt;
       assertEqual( foo(v13, v23, v32), foo_reference9)&lt;br /&gt;
:Man überzeugt sich leicht, dass diese 9 Tests jede mögliche Paarung genau einmal enthalten. Hat der Algorithmus 4 Parameter, benötigt man einen zweiten Latin Square, der zum ersten orthogonal ist. Zwei Latin Squares P und Q heißen orthogonal, wenn alle Paare c&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt;=(P&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt;, Q&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt;) eindeutig sind, d.h. es gilt c&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt;&amp;amp;ne;c&amp;lt;sub&amp;gt;kl&amp;lt;/sub&amp;gt; falls i&amp;amp;ne;k und j&amp;amp;ne;l. Ein zu dem obigen P orthogonales Q ist z.B.&lt;br /&gt;
:&amp;lt;math&amp;gt;Q=\begin{pmatrix}1 &amp;amp; 2 &amp;amp; 3 \\&lt;br /&gt;
                        3 &amp;amp; 1 &amp;amp; 2 \\&lt;br /&gt;
                        2 &amp;amp; 3 &amp;amp; 1\end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
: Jetzt bildet man Kombinationen aus 4 Zahlen, indem man zur obigen Tabelle noch eine vierte Zeile hinzufügt, die die aktuellen Einträge von Q für den jeweiligen Zeilen- und Spaltenindex enthält:&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;0&amp;quot; cellpadding=&amp;quot;7&amp;quot; align=&amp;quot;center&amp;quot;&lt;br /&gt;
|-align=&amp;quot;center&amp;quot; &lt;br /&gt;
|&lt;br /&gt;
! Komb. 1&lt;br /&gt;
! Komb. 2&lt;br /&gt;
! Komb. 3&lt;br /&gt;
! Komb. 4&lt;br /&gt;
! Komb. 5&lt;br /&gt;
! Komb. 6&lt;br /&gt;
! Komb. 7&lt;br /&gt;
! Komb. 8&lt;br /&gt;
! Komb. 9&lt;br /&gt;
|-&lt;br /&gt;
!Zahl 1 (Zeilenindex)&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|-&lt;br /&gt;
! Zahl 2 (Spaltenindex)&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|-&lt;br /&gt;
! Zahl 3 (aktueller Matrixeintrag von P)&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|-&lt;br /&gt;
! Zahl 4 (aktueller Matrixeintrag von Q)&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | 1&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
:Es sind immer noch nur 9 Tests nötig, um alle Paarungen zu erzeugen. Der erste und letzte Test sind nun:&lt;br /&gt;
       assertEqual( bar(v11, v21, v31, v41), bar_reference1)&lt;br /&gt;
       ...&lt;br /&gt;
       assertEqual( bar(v13, v23, v32, v41), bar_reference9)&lt;br /&gt;
:Die Methode der Latin Squares  funktioniert auch, wenn mehr als 3 Belegungen für jeden Parameter möglich sind, und wenn es mehr als 4 Parameter gibt. Für die Einzelheiten verweisen wir auf die Literatur, z.B. [http://citeseer.ist.psu.edu/78354.html], [http://en.wikipedia.org/wiki/Latin_square]. Empirische Untersuchungen haben ergeben, dass die Methode der vollständigen Paarung oft über 90% der Fehler in einem Programm finden kann.&lt;/div&gt;</summary>
		<author><name>Jschleic</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Sortieren&amp;diff=2523</id>
		<title>Sortieren</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Sortieren&amp;diff=2523"/>
				<updated>2008-07-22T08:55:13Z</updated>
		
		<summary type="html">&lt;p&gt;Jschleic: typos&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;----&lt;br /&gt;
== Laufzeitmesung in Python ==&lt;br /&gt;
&lt;br /&gt;
Verwendung der '''timeit-Bibliothek''' für die Hausaufgabe. &lt;br /&gt;
&lt;br /&gt;
* Importiere das timeit-Modul: &amp;lt;tt&amp;gt;import timeit&amp;lt;/tt&amp;gt;&lt;br /&gt;
* Teile den Algorithmus in die Initialisierungen und den Teil, dessen Geschwindigkeit gemessen werden soll. Beide Teile werden in jeweils einen (mehrzeiligen) String eingeschlossen:&lt;br /&gt;
&lt;br /&gt;
  +--------+     +----+            setup = &amp;quot;&amp;quot;&amp;quot;            prog = &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
  |  algo  | --&amp;gt; |init|                +----+                 +----+&lt;br /&gt;
  |        |     +----+                |init|                 |prog|&lt;br /&gt;
  |        |                           +----+                 +----+&lt;br /&gt;
  |        |     +----+             &amp;quot;&amp;quot;&amp;quot;                     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
  |        | --&amp;gt; |prog|            &lt;br /&gt;
  +--------+     +----+            &lt;br /&gt;
&lt;br /&gt;
* aus den beiden Strings wird ein Timeit-Objekt erzeugt: &amp;lt;tt&amp;gt;t = timeit.Timer(prog, setup)&amp;lt;/tt&amp;gt;&lt;br /&gt;
* Frage: Wie oft soll die Algorithmik wiederholt werden&lt;br /&gt;
:z.B. N = 1000&lt;br /&gt;
* Zeit in Sekunden für N Durchläufe: &amp;lt;tt&amp;gt;K = t.timeit(N)&amp;lt;/tt&amp;gt;&lt;br /&gt;
:Zeit für 1 Durchlauf: K/N&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
3.Stunde am 16.04.2008&lt;br /&gt;
&lt;br /&gt;
==Sortierverfahren==&lt;br /&gt;
&lt;br /&gt;
=== Motivation ===&lt;br /&gt;
'''Def:''' &lt;br /&gt;
Ein Sortierverfahren ist ein Algorithmus, der dazu dient, eine Liste von Elementen zu sortieren.&lt;br /&gt;
* Literatur, siehe Sortierverfahren; Bubblesort 1956, Quicksort 1962. Librarysort 2004  &lt;br /&gt;
&lt;br /&gt;
'''Anwendungen'''&lt;br /&gt;
* Sortierte Daten sind häufig Vorbedingungen für Suchverfahren (Speziell für effiziente Suchalgorithmen mit Komplexität &amp;lt;math&amp;gt;\mathcal{O}(log(N))&amp;lt;/math&amp;gt;)&lt;br /&gt;
* Darstellung von Daten gemäß menschlicher Wahrnehmung &lt;br /&gt;
* Aus programmiertechnischer Anwendungssicht hat das Sortierproblem allerdings heute an Relevanz verloren da&lt;br /&gt;
** gängige Programmiersprachen heute typunabhängige Algorithmen zur Verfügung stellen. Der Programmierer braucht sich deshalb in den meisten Fällen nicht mehr um die Implementierung von Sortieralgorithmen zu kümmern. In C/C++ sorgen dafür beispielsweise Methoden aus der [http://de.wikipedia.org/wiki/Standard_Template_Library STL].&lt;br /&gt;
** Festplatten / Hauptspeicher heute weniger limitierenden Charakter haben, so dass Standardsortierverfahren meist ausreichen, während komplizierte, speicher-sparende Sortieralgorithmen nur noch selten benötigt werden.&lt;br /&gt;
* Die Kenntnis grundlegender Sortieralgorithmen ist trotzdem immer noch nötig: Einerseits kann man vorgefertigte Bausteine nur dann optimal einsetzen, wenn man weiß, was hinter den Kulissen passiert und andererseits verdeutlicht gerade das Sortierproblem wichtige Prinzipien der Algorithmenentwicklung und -analyse in sehr anschaulicher Form.&lt;br /&gt;
&lt;br /&gt;
=== Vorraussetzungen/ Spielregeln ===&lt;br /&gt;
&lt;br /&gt;
==== Mengentheoretische  Anforderungen====&lt;br /&gt;
Definition Totale Ordnung/ Total gordnete Menge:&lt;br /&gt;
Eine Totale Ordnung / Total geordnete Menge ist eine binäre Relation    &lt;br /&gt;
&amp;lt;math&amp;gt;R \subseteq M \times M&amp;lt;/math&amp;gt; über einer Menge &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt;, die transitiv, antisymmetrisch und total ist.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; sei  dargestellt als infix Notation &amp;lt;math&amp;gt;\le &amp;lt;/math&amp;gt; dann, falls M total geordnet, gilt &lt;br /&gt;
&amp;lt;math&amp;gt; \forall a,b,c \ \epsilon M &amp;lt;/math&amp;gt; &amp;lt;br/&amp;gt;&lt;br /&gt;
(1) &amp;lt;math&amp;gt;a \le b \bigwedge b \le a \Rightarrow a=b &amp;lt;/math&amp;gt; (antisymmetrisch)&amp;lt;br/&amp;gt;&lt;br /&gt;
(2) &amp;lt;math&amp;gt;a \le b \bigwedge b \le c \Rightarrow a \le c &amp;lt;/math&amp;gt; (transitiv)&amp;lt;br/&amp;gt;&lt;br /&gt;
(3) &amp;lt;math&amp;gt;a \le b \bigvee b \le a &amp;lt;/math&amp;gt; (total) &amp;lt;br/&amp;gt;&lt;br /&gt;
Bemerkung: aus (3) folgt &amp;lt;math&amp;gt; a \le a &amp;lt;/math&amp;gt; (reflexiv) &amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
''Hab in der Wiki eine gute Seite dazu gefunden [http://de.wikipedia.org/wiki/Ordnungsrelation'' Ordnungsrelation]&lt;br /&gt;
&lt;br /&gt;
==== Datenspeicherung ====&lt;br /&gt;
&lt;br /&gt;
Die Daten liegen typischerweise in Form von Arrays oder verketteten Listen vor. Ja nach Datenstruktur sind andere Sortieralgorithmen am besten geeignet. &lt;br /&gt;
;Array:&lt;br /&gt;
        +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
        |///|   |   |   |   |   |   |   |///|  &lt;br /&gt;
        +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
       \________________  ____________________/&lt;br /&gt;
                        \/&lt;br /&gt;
                        N&lt;br /&gt;
Datenelemente können über Indexoperation a[i] gelesen, überschrieben und miteinander vertauscht werden. Vorteil: Die Zugriffsreihenfolge auf die Datenelemente ist beliebig. Nachteil: Einfügen oder Löschen von Elementen aus dem Array ist relativ aufwändig.&lt;br /&gt;
&lt;br /&gt;
;Vekettete Liste:  &lt;br /&gt;
        +---+     +---+     +---+&lt;br /&gt;
        |   | --&amp;gt; |   | --&amp;gt; |   | --&amp;gt; Ende &lt;br /&gt;
        +---+     +---+     +---+&lt;br /&gt;
   &lt;br /&gt;
Jeder Knoten der Liste enthält ein Datenelement und einen Zeiger auf den nächsten Knoten. Vorteil: Einfügen und Löschen von Elementen ist effizient möglich. Nachteil: effizienter Zugriff nur auf den Nachfolger eines gegebenen Elements, d.h. Zugriffsreihenfolge ist nicht beliebig.&lt;br /&gt;
&lt;br /&gt;
==== Stabilität ====&lt;br /&gt;
&lt;br /&gt;
Ein Sortierverfahren heißt ''stabil'' falls die relative Reihenfolge gleicher Schlüssel durch die Sortierung nicht verändert wird.&lt;br /&gt;
&lt;br /&gt;
Beispiel: Sortiere eine Liste von Paaren &amp;lt;tt&amp;gt;[(3,7), (4,2), (4,1), (2,2), (2,8)]&amp;lt;/tt&amp;gt;, wobei die Reihenfolge nur durch das erste Element (Schlüsselelement) jeden Paares festgelegt wird.&lt;br /&gt;
Dann erzeugt ein stabiles Sortierverfahren die Ausgabe&lt;br /&gt;
 [(2,2), (2,8), (3,7), (4,2), (4,1)]&lt;br /&gt;
während die Ausgabe&lt;br /&gt;
 [(2,2), (2,8), (3,7), (4,1), (4,2)]&lt;br /&gt;
nicht stabil ist (die Paare &amp;lt;tt&amp;gt;(4,1), (4,2)&amp;lt;/tt&amp;gt; sind vertauscht).&lt;br /&gt;
&lt;br /&gt;
==== Charakterisierung der Effizienz von Algorithmen ====&lt;br /&gt;
  &lt;br /&gt;
:(a) Komplexität O(   1), O(n), etc. wird in Kapitel [[Effizienz]] erklärt.&lt;br /&gt;
:(b) Zählen der notwendigen Vergleiche&lt;br /&gt;
:(c) Messen der Laufzeit mit 'timeit' (auf identischen Daten)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Rekursive Beziehungen'''&lt;br /&gt;
zerlegt die ursprünglichen Probleme in kleinere Probleme und wendet den Algorithmus auf die kleineren Probleme an; daraufhin werden die Teilprobleme zur Lösung des Gesamtproblems verwendet. &lt;br /&gt;
d.h. Laufzeit (operativer Vergleich) für N Eingaben hängt von der Laufzeit der Eingaben für die Teilprobleme &lt;br /&gt;
&lt;br /&gt;
'''Aufwand'''&lt;br /&gt;
&lt;br /&gt;
(i) rekursives/ lineares Durchlaufen der Eingabedaten, Bearbeitung einzelner Elemente&lt;br /&gt;
&lt;br /&gt;
 C(N)= C(N-1)+ N ;  N&amp;gt;1, C(1)= 1             +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
     = C(N-2) +(N-1)+ N                      | 7  | 3 | 2 | 5 | 6 | 8 | 1 | 4 | 2 |  &lt;br /&gt;
     = C(N-3) + (N-2) + (N-1) + N            +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
     = ...                                         ________________________/&lt;br /&gt;
     = C(1) + 2+...+(N-1) +N                     /&lt;br /&gt;
                                               +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
        N(N+1)   N²                            | 1 | 3 | 2 | 5 | 6 | 8 | 7 | 4 | 2 |  &lt;br /&gt;
      = -----  ~ --                            +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
          2       2                   &lt;br /&gt;
            &lt;br /&gt;
                                                      &lt;br /&gt;
                                               &lt;br /&gt;
   &lt;br /&gt;
&lt;br /&gt;
(ii) rekursives halbieren der Menge der Eingabedaten&lt;br /&gt;
    &lt;br /&gt;
 C(N)= C(N/2)+1 ; N&amp;gt;1, C(1)=0&lt;br /&gt;
 Aus Gründen der Einfachheit sei N  = 2n&lt;br /&gt;
 &lt;br /&gt;
 C(N)= C(2^n)= C(&amp;lt;math&amp;gt;2^{n-1}&amp;lt;/math&amp;gt;) + 1       &lt;br /&gt;
                     &lt;br /&gt;
              = C(&amp;lt;math&amp;gt;2^{n-1}&amp;lt;/math&amp;gt;) + 1 + 1   &lt;br /&gt;
              = ...                    &lt;br /&gt;
                                       &lt;br /&gt;
              = C(&amp;lt;math&amp;gt;2^0&amp;lt;/math&amp;gt;) + n               &lt;br /&gt;
              = n                    &lt;br /&gt;
              = &amp;lt;math&amp;gt;log_2 N&amp;lt;/math&amp;gt;                 &lt;br /&gt;
 +---+---+---+---+-|-+---+---+---+---+ &lt;br /&gt;
 |   |    |   |   |   |   |    |   |   |&lt;br /&gt;
 +---+---+---+---+-|-+---+---+---+---+&lt;br /&gt;
 +---+---+---+---+&lt;br /&gt;
 |   |    |   |   |&lt;br /&gt;
 +---+---+---+---+&lt;br /&gt;
 +---+---+        +---+&lt;br /&gt;
 |   |    |  -&amp;gt;   |   |&lt;br /&gt;
 +---+---+        +---+&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
(iii) rekursives halbieren, lineare Bearbeitung, jedes Elements&lt;br /&gt;
  &lt;br /&gt;
 C(N)= 2C(N/2)+ N; N&amp;gt;1, C(1)= 0&lt;br /&gt;
 Sei N= &amp;lt;math&amp;gt;2^n&amp;lt;/math&amp;gt;&lt;br /&gt;
 C(N)= C(&amp;lt;math&amp;gt;2^n&amp;lt;/math&amp;gt;)= 2C (&amp;lt;math&amp;gt;2^{n-1}&amp;lt;/math&amp;gt;)+ &amp;lt;math&amp;gt;2^n&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;=&amp;gt; &amp;lt;math&amp;gt; \cfrac{C(2^n)}{2^n}&amp;lt;/math&amp;gt; = &amp;lt;math&amp;gt; \cfrac{2C(2^{n-1})}{2^{n-1}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 = &amp;lt;math&amp;gt; \cfrac{2C(2^{n-2})+2^{n-1}}{2^{n-1}}+1&amp;lt;/math&amp;gt;&lt;br /&gt;
 = &amp;lt;math&amp;gt; \cfrac{2C(2^{n-2})}{2^{n-2}}+1 +1&amp;lt;/math&amp;gt;&lt;br /&gt;
 =...&lt;br /&gt;
 = n&lt;br /&gt;
&amp;lt;=&amp;gt; C(&amp;lt;math&amp;gt;2^n&amp;lt;/math&amp;gt;)= &amp;lt;math&amp;gt;2^n&amp;lt;/math&amp;gt; * n&lt;br /&gt;
&amp;lt;=&amp;gt; C= N log&amp;lt;math&amp;gt;_2&amp;lt;/math&amp;gt;N&lt;br /&gt;
&lt;br /&gt;
==Selection Sort==&lt;br /&gt;
&lt;br /&gt;
===Algorithmus===&lt;br /&gt;
&lt;br /&gt;
 array = [...]  # zu sortierendes Array&lt;br /&gt;
 &lt;br /&gt;
 for i in range(len(array)-1):&lt;br /&gt;
    min = i&lt;br /&gt;
    for j in range(i+1, len(array)):&lt;br /&gt;
       if a[j]&amp;lt; a[min]:&lt;br /&gt;
           min = j&lt;br /&gt;
    a[i], a[min] = a[min], a[i]  # Vertausche a[i] mit dem kleinsten rechts befindlichen Element&lt;br /&gt;
                                 # Elemente links von a[i] und a[i] selbst befinden sich nun in ihrer endgültigen Position&lt;br /&gt;
&lt;br /&gt;
Beispiel: Sortieren der Liste &amp;lt;tt&amp;gt;[S,O,R,T,I,N,G]&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
 erste Iteration der äußeren Schleife, Zustand ''vor'' dem Vertauschen:&lt;br /&gt;
  i=0                     min&lt;br /&gt;
 +---+---+---+---+---+---+---+&lt;br /&gt;
 | S | O | R | T | I | N | G |&lt;br /&gt;
 +---+---+---+---+---+---+---+&lt;br /&gt;
 &lt;br /&gt;
 erste Iteration der äußeren Schleife, Zustand ''nach'' dem Vertauschen:&lt;br /&gt;
 +---|---+---+---+---+---+---+&lt;br /&gt;
 | G | O | R | T | I | N | S |&lt;br /&gt;
 +---|---+---+---+---+---+---+&lt;br /&gt;
 &lt;br /&gt;
 zweite Iteration der äußeren Schleife:&lt;br /&gt;
      i=1         min&lt;br /&gt;
 +---|---+---+---+---+---+---+&lt;br /&gt;
 | G | O | R | T | I | N | S |&lt;br /&gt;
 +---|---+---+---+---+---+---+&lt;br /&gt;
 &lt;br /&gt;
 weitere Iterationen:&lt;br /&gt;
          i=2         min&lt;br /&gt;
 +---+---|---+---+---+---+---+&lt;br /&gt;
 | G | I | R | T | O | N | S |&lt;br /&gt;
 +---+---|---+---+---+---+---+ &lt;br /&gt;
 &lt;br /&gt;
              i=3 min&lt;br /&gt;
 +---+---+---|---+---+---+---+&lt;br /&gt;
 | G | I | N | T | O | R | S |&lt;br /&gt;
 +---+---+---|---+---+---+---+ &lt;br /&gt;
 &lt;br /&gt;
                  i=4 min&lt;br /&gt;
 +---+---+---+---+---+---+---+&lt;br /&gt;
 | G | I | N | O | T | R | S |&lt;br /&gt;
 +---+---+---+---+---+---+---+  &lt;br /&gt;
 ...&lt;br /&gt;
&lt;br /&gt;
===Laufzeit===&lt;br /&gt;
&lt;br /&gt;
Da in jeder Iteration der ''inneren'' Schleife ein Vergleich &amp;lt;tt&amp;gt;a[j]&amp;lt; a[min]&amp;lt;/tt&amp;gt; durchgeführt wird, ist die Anzahl der Vergleiche ein gutes Maß für den Aufwand des Algorithmus und damit für die Laufzeit. Sei C(N) die Anzahl der notwendigen Vergleiche, um ein Array der Größe N zu sortieren. Die Arbeitsweise des Algorithmus kann dann so beschrieben werden: Führe N-1 Vergleiche aus, bringe das kleinste Element an die erste Stelle, und fahre mit dem Sortieren des Rest-Arrays (Größe N-1) rechts des ersten Elements fort. Dafür sind nach Definition noch C(N-1) Vergleiche nötig. Es gilt also:&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = C(N-1) + (N-1)&amp;lt;/math&amp;gt;&lt;br /&gt;
C(N-1) können wir nach der gleichen Formel einsetzen, und erhalten:&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = C(N-2) + (N-2) + (N-1)&amp;lt;/math&amp;gt;&lt;br /&gt;
Wir können in dieser Weise weiter fortfahren. Bei C(1) wird das Einsetzen beendet, denn für ein Array der Länge 1 sind keine Vergleiche mehr nötig, also C(1) = 0. Wir erhalten somit&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = C(N-3) + (N-3) + (N-2) + (N-1)&amp;lt;/math&amp;gt;&lt;br /&gt;
:::&amp;lt;math&amp;gt;...&amp;lt;/math&amp;gt;&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = C(1) + 1 + 2 + ...+ (N-2)+ (N-1)&amp;lt;/math&amp;gt;&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = 0 + 1 + 2 + ...+ (N-2)+ (N-1)&amp;lt;/math&amp;gt;&lt;br /&gt;
Nach der Gaußschen Summenformel ist dies&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = \frac {(N-1)N}{2}\approx \cfrac {(N^2)}{2}&amp;lt;/math&amp;gt; (für große N).&lt;br /&gt;
&lt;br /&gt;
In jedem Durchlauf der äußeren Schleife werden außerdem zwei Elemente ausgetauscht. Es gilt für die Anzahl der Austauschoperationen&lt;br /&gt;
:::&amp;lt;math&amp;gt;A(N)= N-1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Stabilität===&lt;br /&gt;
&lt;br /&gt;
Selection Sort ist stabil, wenn die Vergleiche durch &amp;lt;tt&amp;gt;a[j] &amp;lt; a[min]&amp;lt;/tt&amp;gt; erfolgen, weil dann immer das erste Element mit einem gegebenen Schlüssel als erster nach vorn gebracht wird. Bei Vergleichen &amp;lt;tt&amp;gt;a[j] &amp;lt;= a[min]&amp;lt;/tt&amp;gt; wird hingegen das letzte Element zuerst nach vorn gebracht, somit ist Selection Sort dann nicht stabil.&lt;br /&gt;
&lt;br /&gt;
==Insertion Sort==&lt;br /&gt;
&lt;br /&gt;
* wird in der Übungsgruppe behandelt, siehe auch in der [http://de.wikipedia.org/wiki/Insertionsort WikiPedia]&lt;br /&gt;
* Erweiterung: [http://en.wikipedia.org/wiki/Shell_sort Shell sort]&lt;br /&gt;
&lt;br /&gt;
== Mergesort ==&lt;br /&gt;
&lt;br /&gt;
===Algorithmus ===&lt;br /&gt;
&lt;br /&gt;
Zugrunde liegende Idee: &lt;br /&gt;
* Zerlege das Problem in zwei möglichst gleich große Teilprobleme (&amp;quot;Teile und herrsche&amp;quot;-Prinzip -- divide and conquer)&lt;br /&gt;
* Löse die Teilprobleme rekursiv&lt;br /&gt;
* Führe die Teillösungen über Mischen (merging) in richtig sortierter Weise zusammen.&lt;br /&gt;
Der Algorithmus besteht somit aus zwei Teilen&lt;br /&gt;
&lt;br /&gt;
====Zusammenführen -- merge====&lt;br /&gt;
&lt;br /&gt;
a und b sind zwei sortierte Listen, die in eine sortierte Ergebnisliste kombiniert werden.&lt;br /&gt;
&lt;br /&gt;
 def merge(a,b):&lt;br /&gt;
     c = []   # zunächst leere Ergebnisliste &lt;br /&gt;
     i, j = 0, 0&lt;br /&gt;
     while i &amp;lt; len(a) and j &amp;lt; len(b):&lt;br /&gt;
         # wähle des kleinste der noch nicht angefügten Elemente&lt;br /&gt;
         if a[i] &amp;lt;= b[j]:&lt;br /&gt;
              c.append(a[i])&lt;br /&gt;
              i += 1&lt;br /&gt;
         else:&lt;br /&gt;
              c.append(b[j])&lt;br /&gt;
              j += 1&lt;br /&gt;
    # eine Liste ist jetzt aufgebraucht =&amp;gt; der Rest der anderen wird einfach an c angehängt&lt;br /&gt;
    if i &amp;lt; len(a):&lt;br /&gt;
        c += a[i:]&lt;br /&gt;
    else:&lt;br /&gt;
        c += b[j:]&lt;br /&gt;
    return c&lt;br /&gt;
&lt;br /&gt;
====rekursives Sortieren====&lt;br /&gt;
&lt;br /&gt;
 def mergeSort(a):  # a ist das zu sortierende Array&lt;br /&gt;
     if len(a) &amp;lt;= 1:&lt;br /&gt;
         return a   # Rekursionsabschluß: leere Arrays und Arrays mit einem Element müssen nicht sortiert werden&lt;br /&gt;
     else:&lt;br /&gt;
         left  = a[:len(a)/2]   # linkes Teilarray&lt;br /&gt;
         right = a[len(a)/2:]   # rechtes Teilarray&lt;br /&gt;
         leftSorted  = mergeSort(left)  # rekursives Sortieren der Teilarrays&lt;br /&gt;
         rightSorted = mergeSort(right) # ...&lt;br /&gt;
         return merge(leftSorted, rightSorted)  # Zusammenführen der Teilarrays&lt;br /&gt;
&lt;br /&gt;
Bei der Sortierung mit Mergesort wird das Array immer in zwei Teile geteilt. → Es entsteht ein Binärbaum der Tiefe &amp;lt;math&amp;gt;\log_2 N&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Beispiel: Sortieren der Liste &amp;lt;tt&amp;gt;[S,O,R,T,I,N,G]&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Der Algorithmus läuft in der folgenden Skizze zunächst rekursiv von unten nach oben (Zerlegen in Teillisten), danach werden die sortierten Teillisten von oben nach unten zusammengeführt (diese sortierten Teillisten sind in der Skizze dargestellt).&lt;br /&gt;
&lt;br /&gt;
 Schritt 0:&lt;br /&gt;
  S 0 R T I N G     S    O R    T I    N     G    #Arraylänge: N/8    Vergleiche: 0&lt;br /&gt;
 Schritt 1:          \  /   \  /   \  /     /&lt;br /&gt;
  OS RT IN G          OS     RT     IN     /      #Arraylänge: N/4    Vergleiche: 3 * 2 = 6&lt;br /&gt;
 Schritt 2:             \    /        \   /       &lt;br /&gt;
  ORST GIN               ORST          GIN        #Arraylänge: N/2    Vergleiche: 4 + 3 = 7&lt;br /&gt;
                            \         /&lt;br /&gt;
 Schritt3:                   \       /&lt;br /&gt;
  GINORST                     GINORST             #Arraylänge: N      Vergleiche: N     = 7&lt;br /&gt;
&lt;br /&gt;
===Laufzeit ===&lt;br /&gt;
&lt;br /&gt;
Man erkennt an der Skizze, dass der Rekursionsbaum für ein Array der Länge N die Tiefe log N hat. Auf jeder Ebene werden weniger als N Vergleiche ausgeführt, so dass insgesamt weniger als N*log N Vergleiche benötigt werden. Dies ist natürlich wesentlich effizienter als die (N-1)*N/2 Vergleiche von Selection Sort. Mathematisch exakt kann man die Anzahl der Vergleiche durch die folgende Rekursionsformel berechnen:&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = C(\lfloor N/2\rfloor) + C(\lceil N/2\rceil) + N&amp;lt;/math&amp;gt;&lt;br /&gt;
Der Aufwand ergibt sich aus dem Aufwand für die beiden Teilprobleme plus dem Aufwand für N Vergleiche beim Zusammenführen der sortierten Teillisten. Dabei stehen die Zeichen &amp;lt;math&amp;gt;\lfloor \rfloor&amp;lt;/math&amp;gt; und &amp;lt;math&amp;gt;\lceil \rceil&amp;lt;/math&amp;gt; für abrunden bzw. aufrunden, weil ein Problem mit ungeradem N nicht in zwei exakt gkeiche Teile geteilt werden kann. Um diese Komplikation zu vermeiden, beschränken wir uns im folgenden auf den Fall &amp;lt;math&amp;gt;N = 2^n&amp;lt;/math&amp;gt; (mit etwas höherem Aufwand kann man zeigen, dass diese Einschränkung nicht notwendig ist und die Resultate für alle N gelten). Die vereinfachte Aufwandsformel lautet:&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = 2 C(N/2) + N&amp;lt;/math&amp;gt;&lt;br /&gt;
Durch Einsetzen der Formel für N/2 erhalten wir:&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = 2 (2 C(N/4) + N/2) + N = 4 C(N/4) + N + N&amp;lt;/math&amp;gt;&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = 4 (2 C(N/8) + N/4) + N + N = 8 C(N/8) + N + N + N&amp;lt;/math&amp;gt;&lt;br /&gt;
:::&amp;lt;math&amp;gt;...&amp;lt;/math&amp;gt;&lt;br /&gt;
Die Rekursion endet, weil für ein Array der Größe &amp;lt;math&amp;gt;N=1&amp;lt;/math&amp;gt; keine Vergleiche mehr benötigt werden, also &amp;lt;math&amp;gt;C(1) = 0&amp;lt;/math&amp;gt; gilt. Mit &amp;lt;math&amp;gt;N=2^n&amp;lt;/math&amp;gt; ist dies aber gerade nach &amp;lt;math&amp;gt;n = \log_2 N&amp;lt;/math&amp;gt; Zerlegungen der Fall. Merge Sort benötigt also&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = N + ... + N = n \cdot N = N\cdot \log_2 N&amp;lt;/math&amp;gt;&lt;br /&gt;
Vergleiche.&lt;br /&gt;
&lt;br /&gt;
===Weitere Eigenschaften von MergeSort ===&lt;br /&gt;
&lt;br /&gt;
* Mergesort ist '''stabil''': wegen des Vergleichs &amp;lt;tt&amp;gt;a[i] &amp;lt;= b[j]&amp;lt;/tt&amp;gt; wird die Position gleicher Schlüssel im Algorithmus &amp;lt;tt&amp;gt;merge(a,b)&amp;lt;/tt&amp;gt; nicht verändert -- bei gleichem Schlüssel hat, wie gefordert, das linke Element Vorrang.&lt;br /&gt;
* Mergesort ist '''unempfindlich gegenüber der ursprünglichen Reihenfolge der Eingabedaten'''. Grund dafür ist&lt;br /&gt;
** die vollständige Aufteilung des Ausgangsarrays in Arrays der Länge 1 und&lt;br /&gt;
** dass &amp;lt;tt&amp;gt;merge(a,b)&amp;lt;/tt&amp;gt; die Vorsortierung nicht ausnutzt, d.h. die Komplexität von &amp;lt;tt&amp;gt;merge(a,b)&amp;lt;/tt&amp;gt; ist sortierungsunabhängig.&lt;br /&gt;
* Diese Eigenschaft kann unerwünscht sein, wenn ein Teil des Arrays oder gar das ganze Array schon sortiert ist. Es wird nämlich in jedem Fall das ganze Array neu sortiert.&lt;br /&gt;
* Merge Sort eignet sich für das Sortieren von '''verketteten Listen''', weil die Listenelemente stets von vorn nach hinten durchlaufen werden. In diesem Fall muss &amp;lt;tt&amp;gt;merge(a, b)&amp;lt;/tt&amp;gt; keine neue Liste &amp;lt;tt&amp;gt;c&amp;lt;/tt&amp;gt; für das Ergebnis anlegen, sondern kann einfach die Verkettung der Listenelemente von &amp;lt;tt&amp;gt;a&amp;lt;/tt&amp;gt; und &amp;lt;tt&amp;gt;b&amp;lt;/tt&amp;gt; entsprechend anpassen. In diesem Sinne arbeitet Merge Sort auf verketten Listen &amp;quot;in place&amp;quot;, d.h. es wird kein zusätzlicher Speicher benötigt.&lt;br /&gt;
* Im Gegensatz dazu benötigt &amp;lt;tt&amp;gt;merge(a,b)&amp;lt;/tt&amp;gt; zusätzlichen Speicher für das Ergebnis &amp;lt;tt&amp;gt;c&amp;lt;/tt&amp;gt;, wenn die Daten in einem Array gegeben sind.&lt;br /&gt;
&lt;br /&gt;
== Quicksort ==&lt;br /&gt;
&lt;br /&gt;
* Quicksort wurde in den 60er Jahren von Charles Antony Richard Hoare [http://de.wikipedia.org/wiki/C._A._R._Hoare] entwickelt. Es gibt viele Implementierungen von Quicksort, vgl. [http://de.wikipedia.org/wiki/Quicksort].&lt;br /&gt;
* Dieser Algorithmus gehört zu den &amp;quot;Teile und herrsche&amp;quot;-Algorithmen (divide-and-conquer) und ist der Standardalgorithmus für Sortieren.&lt;br /&gt;
* Im Gegensatz zu Merge Sort wird das Problem aber nicht immer in zwei fast gleich große Teilprobleme zerlegt. Dadurch vermeidet man, dass zusätzlicher Speicher benötigt wird (Quick Sort arbeitet auch für Arrays &amp;quot;in place&amp;quot;). Allerdings erkauft man sich dies dadurch, dass Quick Sort bei ungünstigen Eingaben (die Bedeutung von &amp;quot;ungünstig&amp;quot; ist je nach Implementation verschieden) nicht effizient arbeitet. Da solche Eingaben jedoch in der Praxis fast nie vorkommen, tut dies der Beliebtheit von Quicksort keinen Abbruch.&lt;br /&gt;
&lt;br /&gt;
=== Algorithmus===&lt;br /&gt;
&lt;br /&gt;
Wie Merge Sort arbeitet Quick Sort rekursiv. Hier werden die Daten allerdings zuerst vorbereitet (in der Funktion &amp;lt;tt&amp;gt;partition&amp;lt;/tt&amp;gt;), und danach erfolgt der rekursive Aufruf:&lt;br /&gt;
&lt;br /&gt;
 def quicksort(a, l, r): &lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;a ist das zu sortierende Array, &lt;br /&gt;
        l und r sind die linke und rechte Grenze des zu sortierenden Bereichs&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
      if r &amp;gt; l:                     # Rekursionsabschluss: wenn r &amp;lt;= l, ist der Bereich leer und muss nicht mehr sortiert werden&lt;br /&gt;
          i = partition(a, l, r)    # i ist der Index des sog. Pivot-Elements (s. u.)&lt;br /&gt;
          quicksort(a, l, i-1)      # rekursives Sortieren der beiden Teilarrays&lt;br /&gt;
          quicksort(a, i+1, r)      # ...&lt;br /&gt;
&lt;br /&gt;
Der Schlüssel des Algorithmus ist offensichtlich die Funktion &amp;lt;tt&amp;gt;partition&amp;lt;/tt&amp;gt;. Diese wählt ein Element des Arrays aus (das Pivot-Element) und bringt es an die richtige Stelle (also an den Index &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt;, der von &amp;lt;tt&amp;gt;partition&amp;lt;/tt&amp;gt; zurückgegeben wird). Ausserdem stellt sie sicher, dass alle Elemente in der linken Teilliste (Index &amp;lt; &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt;) kleiner als &amp;lt;tt&amp;gt;a[i]&amp;lt;/tt&amp;gt;, und alle Elemente in der rechten Teilliste größer also &amp;lt;tt&amp;gt;a[i]&amp;lt;/tt&amp;gt; sind:&lt;br /&gt;
# &amp;lt;math&amp;gt;a[i]&amp;lt;/math&amp;gt; ist sortiert, d.h. dieses Element ist am endgültigen Platz.&lt;br /&gt;
# &amp;lt;math&amp;gt;\forall x \in \left\{ a \left[ l \right] , ... a \left[ i-1 \right] \right\} : x \leq a \left[ i \right]&amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt;\forall x \in \left\{ a \left[ i+1 \right], ... a \left[ r \right] \right\} : x \geq a \left[ i \right]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
          l                               r&lt;br /&gt;
        +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 Array: |   |   |   |   |\\\|   |   |   |   |&lt;br /&gt;
        +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
         \______  _____/  i  \______  _____/ &lt;br /&gt;
                \/                  \/&lt;br /&gt;
             &amp;lt;=a[i]               &amp;gt;=a[i]             (a[i] ist das Pivot-Element)&lt;br /&gt;
&lt;br /&gt;
Die Position von &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt; richtet sich also offensichtlich danach, wie viele Elemente im Bereich &amp;lt;tt&amp;gt;l&amp;lt;/tt&amp;gt; bis &amp;lt;tt&amp;gt;r&amp;lt;/tt&amp;gt; kleiner bzw. größer als das gewählte Pivot-Element sind. Der Wahl eines guten Pivot-Elements kommt demnach eine große Bedeutung zu (s.u.). &lt;br /&gt;
&lt;br /&gt;
In der einfachsten Version wird &amp;lt;tt&amp;gt;partition&amp;lt;/tt&amp;gt; wie folgt definiert:&lt;br /&gt;
&lt;br /&gt;
 def partition(a, l, r):&lt;br /&gt;
     pivot = a[r]     # Pivot-Element. Hier wird willkürlich das letzte Element verwendet.&lt;br /&gt;
     i  = l           # i und j sind Laufvariablen&lt;br /&gt;
     j  = r - 1&lt;br /&gt;
 &lt;br /&gt;
     while True:&lt;br /&gt;
         while a[i] &amp;lt;= pivot and i &amp;lt; r:&lt;br /&gt;
             i += 1               # finde von links das erste Element &amp;gt; pivot&lt;br /&gt;
         while a[j] &amp;gt;= pivot and j &amp;gt; l:&lt;br /&gt;
             j -= 1               # finde von rechts den ersten Eintrag &amp;lt;= pivot&lt;br /&gt;
         if i &amp;gt;= j: break         # keine weiteren Elemente zum Tauschen =&amp;gt; Schleife beenden      &lt;br /&gt;
         a[i], a[j] = a[j], a[i]  # a[i] und a[j] sind beide auf der falschen Seite des Pivot =&amp;gt; vertausche sie&lt;br /&gt;
     if a[i] &amp;gt; pivot:&lt;br /&gt;
         a[i], a[r] = a[r], a[i]&lt;br /&gt;
     return i&lt;br /&gt;
&lt;br /&gt;
Die folgende Skizze verdeutlicht das Austauschen&lt;br /&gt;
&lt;br /&gt;
                                          p&lt;br /&gt;
        +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 Array: |   |   |   |   |   |   |   |   |\\\|&lt;br /&gt;
        +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
        ------&amp;gt; a[i]&amp;gt;p          a[j]&amp;lt;p &amp;lt;-----&lt;br /&gt;
                  |               |&lt;br /&gt;
                  +---------------+&lt;br /&gt;
       Diese zwei Elemente werden ausgetauscht.&lt;br /&gt;
 &lt;br /&gt;
Dies wird wiederholt, bis sich die Zeiger treffen oder einander überholt haben. Am Schluss wird das Pivot-Element an die richtige Stelle verschoben:&lt;br /&gt;
 &lt;br /&gt;
                          p&lt;br /&gt;
        +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 Array: |   |   |   |   |\\\|   |   |   |   |&lt;br /&gt;
        +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
                          i&lt;br /&gt;
        -----------------&amp;gt; &amp;lt;-----------------&lt;br /&gt;
  &lt;br /&gt;
Beispiel: Partitionieren des Arrays &amp;lt;tt&amp;gt;[A,S,O,R,T,I,N,G,E,X,A,M,P,L,E]&amp;lt;/tt&amp;gt; mit Pivot 'E'.&lt;br /&gt;
&lt;br /&gt;
  l,i --&amp;gt;                                          &amp;lt;-- j   r&lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 | A | S | O | R | T | I | N | G | E | X | A | M | P | L | E |&lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 &lt;br /&gt;
       i &amp;lt;--------- Vertauschen ---------&amp;gt; j               r&lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 | A | S | O | R | T | I | N | G | E | X | A | M | P | L | E |&lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 &lt;br /&gt;
           i &amp;lt;-------------------&amp;gt; j                       r&lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 | A | A | O | R | T | I | N | G | E | X | S | M | P | L | E |&lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 &lt;br /&gt;
           j   i                                           r&lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 | A | A | E | R | T | I | N | G | O | X | S | M | P | L | E |   --&amp;gt; Hier wird die &lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+       Schleife verlassen.&lt;br /&gt;
 &lt;br /&gt;
           j   i &amp;lt;---------------------------------------&amp;gt; r&lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 | A | A | E | R | T | I | N | G | O | X | S | M | P | L | E |   &lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 &lt;br /&gt;
               i                                           r&lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 | A | A | E | E | T | I | N | G | O | X | S | M | P | L | R |   --&amp;gt; Hier wird partition() beendet.&lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Weitere ausführliche Erklärungen der Implementation findet man bei Sedgewick.&lt;br /&gt;
&lt;br /&gt;
=== Laufzeit===&lt;br /&gt;
&lt;br /&gt;
Wir müssen hier den schlechtesten und den typischen Fall unterscheiden. Der schlechteste Fall tritt ein, wenn das Array bereits sortiert ist. Dann ist das Pivot-Element immer bereits am richtigen Platz, so dass &amp;lt;tt&amp;gt;partition(a, l, r)&amp;lt;/tt&amp;gt; stets den Index &amp;lt;tt&amp;gt;i = r&amp;lt;/tt&amp;gt; zurück. Daher wird das Array niemals in zwei etwa gleichgroße Teile zerlegt. Die Anzahl der Vergleiche ergibt sich als&lt;br /&gt;
&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = (N+1) + C(N-1) + C(0)&amp;lt;/math&amp;gt;&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(0) = 0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
mit (N+1) Vergleichen in &amp;lt;tt&amp;gt;partition()&amp;lt;/tt&amp;gt;. Durch sukzessives Einsetzen erhalten wir:&lt;br /&gt;
&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = (N+1) + (N) + (N-1) + ... + 1 = (N+1) N / 2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In diesem Fall ist Quick Sort also nicht schneller als Selection Sort. Wir beschreiben mögliche Verbesserungen unten. Im typischen Fall (wenn nämlich das Array zufällig sortiert ist) sieht die Situation wesentlich besser aus. Bei zufälliger Sortierung wird jeder Index mit gleicher Wahrscheinlichkeit zur Pivot-Position. Wir mitteln deshalb über alle möglichen Positionen:&lt;br /&gt;
&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N) = (N+1) + \frac{1}{N} \sum_{k=1}^{N} \left[ C(k-1) + C(N-k) \right]&amp;lt;/math&amp;gt; für &amp;lt;math&amp;gt; N&amp;gt;0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
wobei  &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; über alle möglichen Teilungspunkte läuft. Die Summe (der mittlere Aufwand über alle möglichen Zerlegungen) kann vereinfacht werden zu&lt;br /&gt;
:::&amp;lt;math&amp;gt;\frac{1}{N} \sum_{k=1}^{N} \left[ C(k-1) + C(N-k) \right] = 2 \frac{1}{N} \sum_{k=1}^{N} C(k-1) &amp;lt;/math&amp;gt;&lt;br /&gt;
Die Auflösung der Formel ist etwas trickreich. Wir multiplizieren zunächst beide Seiten mit N:&lt;br /&gt;
&lt;br /&gt;
:::&amp;lt;math&amp;gt;&lt;br /&gt;
N \cdot C(N) = N \left[ (N+1) + \frac{2}{N} \sum_{k=1}^{N} C(k-1) \right] = N (N+1) + 2\; \sum_{k=1}^{N} C(k-1)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durch die Substitution &amp;lt;math&amp;gt;N \rightarrow N-1&amp;lt;/math&amp;gt; erhalten wir die entsprechende Formel für N-1:&lt;br /&gt;
&lt;br /&gt;
:::&amp;lt;math&amp;gt;&lt;br /&gt;
(N-1) \cdot C(N-1) = (N-1) N + 2\; \sum_{k=1}^{N-1} C(k-1)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Wir subtrahieren die Formel für N-1 von der Formel für N und eliminieren dadurch die Summe (nur der letzte Summend der ersten Summe bleibt übrig):&lt;br /&gt;
:::&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{rcl}&lt;br /&gt;
N \cdot C(N) - (N-1) \cdot C(N-1) &amp;amp;=&amp;amp; N(N+1) + 2\;\sum_{k=1}^{N} C(k-1)  - (N-1) N - 2\;\sum_{k=1}^{N-1} C(k-1)\\&lt;br /&gt;
&amp;amp;&amp;amp;\\&lt;br /&gt;
N \cdot C(N) - (N-1) \cdot C(N-1) &amp;amp;=&amp;amp; N(N+1) - (N-1) N + 2 C(N-1)&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
Durch Vereinfachen erhalten wir die rekurrente Beziehung&lt;br /&gt;
:::&amp;lt;math&amp;gt;&lt;br /&gt;
N \cdot C(N) = (N+1)\cdot C(N-1) + 2 N&amp;lt;/math&amp;gt;&lt;br /&gt;
Wir teilen jetzt beide Seiten durch &amp;lt;math&amp;gt;(N+1)N&amp;lt;/math&amp;gt;&lt;br /&gt;
:::&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{C(N)}{N+1} = \frac{C(N-1)}{N} + \frac{2}{N+1} &amp;lt;/math&amp;gt;&lt;br /&gt;
Sukzessives Einsetzen der Formel für &amp;lt;math&amp;gt; C(N-1), C(N-2) &amp;lt;/math&amp;gt; etc. bis &amp;lt;math&amp;gt;C(1)=0&amp;lt;/math&amp;gt; liefert&lt;br /&gt;
:::&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{C(N)}{N+1} = \frac{C(N-2)}{N-1} + \frac{2}{N} + \frac{2}{N+1} = \frac{C(2)}{3} + \sum_{k=3}^N\frac{2}{k+1} &amp;lt;/math&amp;gt;&lt;br /&gt;
Für hinreichend große N kann die Summe sehr genau durch ein Integral approximiert werden. Der konstanten Term kann vernachlässigt werden:&lt;br /&gt;
:::&amp;lt;math&amp;gt; &lt;br /&gt;
\frac{C(N)}{N+1} \approx 2 \sum_{k=3}^{N} \frac{1}{k+1} \approx 2 \int_1^N \frac{1}{k} dk = 2 \cdot \ln(N)&amp;lt;/math&amp;gt;&lt;br /&gt;
Somit benötigt Quick Sort im typischen Fall&lt;br /&gt;
:::&amp;lt;math&amp;gt;C(N)\approx 2 N\cdot\ln(N) \approx 1.38 N\cdot\log_2(N)&amp;lt;/math&amp;gt;&lt;br /&gt;
Vergleiche. Quick Sort ist demnach etwa genauso schnell wie Merge Sort (in der Praxis sogar etwas schneller, da die innere Schleife von Quick Sort etwas einfacher ist).&lt;br /&gt;
&lt;br /&gt;
=== Verbesserungen des Quicksort-Algorithmus ===&lt;br /&gt;
&lt;br /&gt;
==== Beseitigung der Rekursion ====&lt;br /&gt;
Eine Verbesserung beseitigt die Rekursion durch Verwendung eines Stacks. Nach jeder Partitionierung wird das größere Teilintervall auf dem Stack abgelegt und das kleinere Teilintervall direkt weiterverarbeitet (hierdurch wird sichergestellt, dass die maximale Größe des Stacks minimiert wird).&lt;br /&gt;
&lt;br /&gt;
 def quicksortNonRecursive(a, l, r):&lt;br /&gt;
     stack = [(l,r)]  # initialisiere den Stack&lt;br /&gt;
     while len(stack) &amp;gt; 0:&lt;br /&gt;
         if r &amp;gt; l:&lt;br /&gt;
             i = partition(a, l, r)&lt;br /&gt;
             if (i-l) &amp;gt; (r-i):&lt;br /&gt;
                 stack.append((l,i-1))&lt;br /&gt;
                 l = i+1&lt;br /&gt;
             else:&lt;br /&gt;
                 stack.append((i+1, r))&lt;br /&gt;
                 r = i-1&lt;br /&gt;
         else:&lt;br /&gt;
             l, r = stack.pop()&lt;br /&gt;
&lt;br /&gt;
Die ist die Methode der ''Endrekursionsbeseitigung'', die wir im Kapitel [[Iteration versus Rekursion]] ausführlich behandeln. Die folgende Skizze verdeutlicht die Verwendung des Stacks.&lt;br /&gt;
&lt;br /&gt;
 +---+---+---+---+---+---+---+&lt;br /&gt;
 | Q | U | I | C | K | S | O |&lt;br /&gt;
 +---+---+---+---+---+---+---+&lt;br /&gt;
 &lt;br /&gt;
              &lt;br /&gt;
 +---+---+---+===+---+---+---+&lt;br /&gt;
 | K | C | I |=O=| Q | S | U |&lt;br /&gt;
 +---+---+---+===+---+---+---+&lt;br /&gt;
                  \_________/&lt;br /&gt;
                      push&lt;br /&gt;
 &lt;br /&gt;
 +---+===+---+&lt;br /&gt;
 | C |=I=| K |&lt;br /&gt;
 +---+===+---+&lt;br /&gt;
          \_/&lt;br /&gt;
          push&lt;br /&gt;
 &lt;br /&gt;
 +===+&lt;br /&gt;
 |=C=|&lt;br /&gt;
 +===+&lt;br /&gt;
 &lt;br /&gt;
         +===+&lt;br /&gt;
         |=K=|&lt;br /&gt;
         +===+&lt;br /&gt;
 &lt;br /&gt;
                 +---+---+===+&lt;br /&gt;
                 | Q | S |=U=|&lt;br /&gt;
                 +---+---+===+&lt;br /&gt;
 &lt;br /&gt;
                 +---+===+&lt;br /&gt;
                 | Q |=S=|&lt;br /&gt;
                 +---+===+&lt;br /&gt;
 &lt;br /&gt;
                 +===+&lt;br /&gt;
                 |=Q=|&lt;br /&gt;
                 +===+&lt;br /&gt;
         &lt;br /&gt;
 +---+---+---+---+---+---+---+&lt;br /&gt;
 | C | I | K | O | Q | S | U |  &lt;br /&gt;
 +---+---+---+---+---+---+---+&lt;br /&gt;
&lt;br /&gt;
==== Alternatives Sortieren kleiner Intervalle ====&lt;br /&gt;
&lt;br /&gt;
Für kleine Arrays (bis zu einer gegebenen Größe K) ist das &amp;quot;Teile und herrsche&amp;quot;-Prinzip nicht die effizienteste Herangehensweise. Insbesondere kann man ein Array mit maximal 3 Elementen direkt sortieren:&lt;br /&gt;
 def sortThree(a, l, r):&lt;br /&gt;
     if r &amp;gt; l and a[l+1] &amp;lt; a[l]:          # Stelle sicher, dass a[l] und a[l+1] relativ zueinander sortiert sind.&lt;br /&gt;
         a[l], a[l+1] = a[l+1], a[l]&lt;br /&gt;
     if r == l + 2:&lt;br /&gt;
         if a[r] &amp;lt; a[l]:                  # Stelle sicher, dass a[l] und a[r] relativ zueinander sortiert sind.&lt;br /&gt;
             a[l], a[r] = a[r], a[l]      # Danach ist a[l] auf jeden Fall das kleinste Element.&lt;br /&gt;
         if a[r] &amp;lt; a[r-1]:                # Stelle sicher, dass a[r-1] und a[r] relativ zueinander sortiert sind.&lt;br /&gt;
             a[r], a[r-1] = a[r-1], a[r]  # Jetzt ist a[r] auf jeden Fall das größte Element und das Array damit sortiert.&lt;br /&gt;
&lt;br /&gt;
In die Funktion &amp;lt;tt&amp;gt;quicksort()&amp;lt;/tt&amp;gt; wird jetzt ein Aufruf dieser Funktion eingefügt:&lt;br /&gt;
     if r &amp;gt; l + 2:&lt;br /&gt;
         # wie bisher&lt;br /&gt;
     elif r &amp;gt; l:&lt;br /&gt;
         sortThree(a, l, r)&lt;br /&gt;
&lt;br /&gt;
==== Günstige Selektion des Pivot-Elements ====&lt;br /&gt;
Durch geschickte Wahl des Pivot-Elements kann man erreichen, dass der ungünstigste Fall (quadratische Laufzeit) nur mit sehr kleiner Wahrscheinlichkeit eintritt. Zwei Möglichkeiten haben sich bewährt:&lt;br /&gt;
# Anstatt des letzten Elements des Teilarrays wählt man ein zufälliges Element (mit Hilfe eines Zufallszahlengenerators). Dadurch wird Quick Sort unempfindlich gegenüber bereits sortierten Arrays, weil die Teilung im Mittel wie bei einem zufällig sortierten Array erfolgt (typischer Fall in obiger Laufzeitberechnung).&lt;br /&gt;
# Median (mittlerer Wert) von drei Elementen: Verwende den Median des ersten, mittleren und letzten Elements jedes Teilarrays als Pivot-Element.&lt;br /&gt;
In beiden Fällen ist es praktisch ausgeschlossen, dass ein Eingabearray so angeordnet ist, dass in jedem Teilarray gerade das kleinste oder größte Element als Pivot gewählt wird. Nur dann könnte der ungünstigste Fall jedoch eintreten, was somit effektiv verhindert wird.&lt;/div&gt;</summary>
		<author><name>Jschleic</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Einf%C3%BChrung&amp;diff=2522</id>
		<title>Einführung</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Einf%C3%BChrung&amp;diff=2522"/>
				<updated>2008-07-22T08:49:39Z</updated>
		
		<summary type="html">&lt;p&gt;Jschleic: /* Definition von Datenstrukturen */  typos&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Definition von Algorithmen ==&lt;br /&gt;
&lt;br /&gt;
Es gibt viele Definitionen von Algorithmen. Hier sind die Ergebnisse einer Google-Suche auf  [http://www.google.de/search?hl=de&amp;amp;defl=en&amp;amp;q=define:Algorithm&amp;amp;sa=X&amp;amp;oi=glossary_definition&amp;amp;ct=title englisch] und auf&lt;br /&gt;
[http://www.google.de/search?hl=de&amp;amp;defl=de&amp;amp;q=define:Algorithmus&amp;amp;sa=X&amp;amp;oi=glossary_definition&amp;amp;ct=title deutsch]. Die Grundidee ist aber immer gleich:&lt;br /&gt;
&lt;br /&gt;
Ein '''Algorithmus''' ist eine Problemlösung durch endlich viele elementare Schritte. Die Teile der Definition bedürfen näherer Erläuterung:&lt;br /&gt;
&lt;br /&gt;
;Problemlösung: Damit ein Algorithmus ein Problem (genauer: eine Menge von gleichartigen Problemen) lösen kann, muss das Problem zunächst definiert (''spezifiziert'') werden. Die '''Spezifikation''' legt fest, ''was'' der Algorithmus erreichen soll, sagt aber nichts über das ''wie''. Die Spezifikation beschreibt somit relevante Eigenschaften des Systemzustands ''vor'' und ''nach'' der Ausführung des Algorithmus (sogenannte '''Vor-''' und '''Nachbedingungen'''), während der Algorithmus einen bestimmten ''Lösungsweg'' repräsentiert. Mit Hilfe der Spezifikation kann gezeigt werden, dass der Algorithmus tatsächlich eine Lösung des gestellten Problems liefert. Diese Frage untersuchen wir im Kapitel [[Korrektheit]].&lt;br /&gt;
;Endlich viele Schritte: Die Forderung nach endlich vielen Schritten unterstellt, dass jeder einzelne Schritt eine gewisse Zeit benötigt, also nicht unendlich schnell ausgeführt werden kann. Damit ist diese Forderung äquivalent zu der Forderung, dass der Algorithmus in endlicher Zeit zum Ergebnis kommen muss. Der Sinn einer solchen Forderung leuchtet aus praktischer Sicht unmittelbar ein. Interessant ist darüber hinaus die Frage, wie man mit möglichst wenigen Schritten, also möglichst schnell, zur Lösung kommt. Diese Frage untersuchen wir im Kapitel [[Effizienz]].&lt;br /&gt;
;Elementare Schritte: Im weiteren Sinne verstehen wir unter einem elementaren Schritt ein Teilproblem, für das bereits ein Algorithmus bekannt ist. Im engeren Sinne ist die Menge der elementaren Schritte durch die Hilfsmittel vorgegeben, mit denen der Algorithmus ausgeführt werden soll, also z.B. durch die Hardware oder die Programmiersprache. Wir gehen darauf im nächsten Abschnitt näher ein.&lt;br /&gt;
&lt;br /&gt;
=== Zur Frage der elementaren Schritte ===&lt;br /&gt;
&lt;br /&gt;
Welche Schritte als elementar angesehen werden können, hängt sehr stark vom Kontext der Aufgabe und den Hilfsmitteln zu ihrer Lösung ab. Ein interessantes Beispiel ist die Geometrie der alten Griechen, wo geometrische Probleme in der Ebene allein mit Zirkel und Lineal gelöst werden. In diesem Fall sind folgende elementare Operationen erlaubt:&lt;br /&gt;
* das Markieren eines Punktes (beliebig in der Ebene oder als Schnittpunkt zwischen bereits gezeichneten Linien),&lt;br /&gt;
* das Zeichnen einer Geraden durch zwei Punkte,&lt;br /&gt;
* das Zeichnen eines Kreises um einen Punkt,&lt;br /&gt;
* das Abgreifen des Abstands zwischen zwei Punkten mit dem Zirkel.&lt;br /&gt;
Auf der Basis dieser Operationen kann zum Beispiel kein Algorithmus für die Dreiteilung eines beliebigen Winkels definiert werden, während der Algorithmus für die Zweiteilung sehr einfach ist. &lt;br /&gt;
&lt;br /&gt;
Eine völlig andere Menge von elementaren Operationen ergibt sich für arithmetische Berechnungen mit Hilfe des Abacus (Rechenbrett), der seit der Römerzeit in Europa weit verbreitet war. Hier werden Zahlen durch die Positionen von Perlen auf Rillen oder Drähten dargestellt und Berechnungen durch deren Verschiebung. Eine ausführliche Beschreibung der wichtigsten Abacus-Algorithmen findet sich unter [http://webhome.idirect.com/~totton/abacus/ The Bead Unbuffled] von Totton Heffelfinger und Gary Flom.&lt;br /&gt;
&lt;br /&gt;
Die moderne Auffassung von elementaren Operationen wird durch die Berechenbarkeitstheorie (ein Teilgebiet der theoretischen Informatik) bestimmt. Verschiedene Mathematiker (darunter die Pioniere Alan Turing, Alonso Church, Kurt Gödel, Stephen Kleene und Emil Post) haben seit den 1930er Jahren versucht, den intuitiven Begriff der Berechenbarkeit einer Funktion zu formalisieren und sind dabei zu völlig verschiedenen Lösungen gelangt (z.B. Turingmaschine, Lambda-Kalkül, μ-Rekursion und WHILE-Programm). Interessanterweise stellte sich heraus, dass diese Lösungen alle die gleiche Mächtigkeit haben: Obwohl die elementaren Operationen jeweils ganz anders definiert sind, ist die Menge der damit berechenbaren Funktionen immer gleich. Die [http://en.wikipedia.org/wiki/Church_thesis Church-Turing-These] besagt, dass es prinzipiell unmöglich ist, eine mächtigere Definition von elementaren Operationen zu finden, aber dies ist unbewiesen. Am bequemsten für die Praxis sind die  [http://de.wikipedia.org/wiki/WHILE-Programm WHILE-Programme], da sie sich direkt auf die heute gebräuchliche Hardware-Architektur abbilden lassen. Die elementaren Operationen eines WHILE-Programms lauten in erweiterter Backus-Naur Notation:&lt;br /&gt;
 P ::= x[i] = x[j] + c&lt;br /&gt;
     | x[i] = x[j] - c&lt;br /&gt;
     | P; P&lt;br /&gt;
     | WHILE x[i] != 0 DO P DONE&lt;br /&gt;
wobei &amp;lt;tt&amp;gt;c&amp;lt;/tt&amp;gt; ein beliebiges ganzahliges Literal (eine ausgeschriebene ganze Zahl) und &amp;lt;tt&amp;gt;x[i]&amp;lt;/tt&amp;gt; die Speicherzelle &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt; bezeichnet. Alle Speicherzellen können ganze Zahlen aufnehmen und sind anfangs mit Null belegt. Darüber hinaus wird vorausgesetzt, dass mindestens soviele Speicherzellen vorhanden sind, wie der gegebene Algorithmus benötigt, und jede Speicherzelle groß genug ist, um die größte auftretende Zahl aufzunehmen. Beide Annahmen sind in der Praxis nicht immer erfüllt.&lt;br /&gt;
&lt;br /&gt;
Die Zerlegung jedes Problems in Form eines WHILE-Programms (oder eines äquivalenten Formalismus der Berechenbarkeitstheorie) ist für unsere Zwecke aber zu feinkörnig: Sie würde bedeuten, dass alle Algorithmen auf einem sehr einfachen Prozessor in Assembler programmiert werden müssten. Statt dessen definiert man ''höhere Programmiersprachen'', die wichtige Algorithmen wie z.B. die arithmetischen Operationen mit ganzen Zahlen und Gleitkomma-Zahlen bereits als elementare Operationen enthalten. Weitere nicht ganz so wichtige Funktionen wie die Wurzel oder der Logarithmus werden in Programmbibliotheken angeboten, die standardmäßig mitgeliefert werden. In der Praxis betrachtet man eine Operation deshalb als elementar, wenn sie von einer typischen Programmiersprache oder einer typischen Standardbibliothek unterstützt wird. In dieser Vorlesung wählen wir die Operationen und Bibliotheken der Programmiersprache [http://www.python.org Python]. Wenn ein Algorithmus Anforderungen stellt, die nicht selbstverständlich sind, müssen sie als ''Requirements'' explizit angegeben werden. Wir werden darauf im Kapitel [[Generizität]] zurückkommen.&lt;br /&gt;
&lt;br /&gt;
=== Zur Geschichte ===&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;0&amp;quot; cellspacing=&amp;quot;0&amp;quot; cellpadding=&amp;quot;5&amp;quot; &lt;br /&gt;
|-valign=&amp;quot;top&amp;quot; &lt;br /&gt;
| Algorithmen wurden bereits im Altertum verwendet. Besonders die alten Griechen haben Pionierarbeit geleistet, z.B. auf dem Gebiet der Arithmetik (Euklidischer Algorithmus für den größten gemeinsamen Teiler von zwei Zahlen, Sieb des Eratosthenes zur Bestimmung von Primzahlen) und der Geometrie (Teilung einer Strecke oder eines Winkels nur mit Zirkel und Lineal). Der Begriff ''Algorithmus'' ist vom Namen des arabischen Gelehrten Muhammed Al Chwarizmi (ca. 783-850) abgeleitet, der in seinem Werk „Über das Rechnen mit indischen Ziffern“ (um 825) grundlegende Verfahren für das Rechnen im dekadischen Positionssystem beschrieben hat. Im 12. Jahrhundert wurde dieses Buch ins Lateinische übersetzt, und die Einleitung begann mit den Worten „Dixit Algorismi“ (Al Chwarizmi hat gesagt). Ab etwa 1200 wurden die neuen Rechenmethoden als „Algorismus de integris“ bzw. „Algorismus vulgaris“ (Rechnen mit ganzen Zahlen, d.h. Grundrechenarten und Wurzelziehen) sowie „Algorismus de minutiis“ (Bruchrechnung) zum festen Bestandteil der mathematischen Ausbildung im Rahmen der sieben freien Künste. Dabei diente der Begriff Algorithmus unrsprünglich vor allem zur Abgrenzung des schriftlichen Rechnens mit indischen/arabischen Zahlen (wie wir es noch heute in der Schule lernen) vom traditionellen mechanischen Rechnen mit Abacus und römischen Zahlen, das noch bis ca. 1500 in Europa vorherrschend blieb. &lt;br /&gt;
&lt;br /&gt;
Die allgemeinere Bedeutung des Wortes Algorithmus als systematische Rechenvorschrift war jedoch ebenfalls schon früh gebräuchlich. Dies zeigt zum Beispiel der Titel des Buches „Algorismus proportionum“ (Rechenkunst mit Proportionen, ca. 1350) von Nicole Oresme, wo erstmals die Rechenregeln für Potenzen mit rationalen Exponenten beschrieben werden. Durch die steigenden Anforderungen des kaufmännischen Rechnens und der Navigation verbreitete sich die algorithmische Denkweise ab etwa 1500 rasch. Der Buchdruck machte mit Werken wie Adam Ries' „Rechenung auff der linihen und federn“ (d.h. mit Abacus und mit indischen/arabischen Zahlen, zuerst 1522) die grundlegenden Rechenalgorithmen einem breiten Bevölkerungskreis bekannt. Umfangreiche gedruckte Tafelwerke, z.B. der „Canon“ von G.J. Rhaeticus (1551) mit bis zu siebenstelligen Tabellen der trigonometrischen Funktionen, erlaubten es, komplizierte Berechnungen auf einfache Schritte (Addition, Subtraktion sowie Nachschlagen in der Tabelle) zurückzuführen. Unsere heutige Verwendung des Begriffs geht wohl auf Alonso Church's Aufsatz „An Unsolvable Problem of Elementary Number Theory“ (1936) zurück, wo die Berechenbarkeit einer Funktion mit der Existenz eines terminierenden Berechnungsalgorithmus gleichgesetzt wird.&lt;br /&gt;
| [[Image:Al-Khwarizmi.jpg]] &amp;lt;br&amp;gt; Standbild Al Chwarizmis in Teheran&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Definition von Datenstrukturen ==&lt;br /&gt;
&lt;br /&gt;
Der Speicher eines Computers enthält eine Folge von Zeichen aus einem gegebenen Alphabet. Bei fast allen heutigen Computern ist dies eine Folge von Bits aus dem Alphabet {0,1}. Eine '''Datenstruktur''' ordnet eine Bitfolge in Gruppen und gibt jeder Gruppe eine Bedeutung. Der Gruppierungsprozess kann dann hierarchisch fortgesetzt werden.&lt;br /&gt;
&lt;br /&gt;
Die selben Bits können somit völlig verschiedene Bedeutungen annehmen, ja nachdem in welcher Datenstruktur sie sich befinden. Man betrachte z.B. die Folge von 32 Bits:&lt;br /&gt;
 11111100011000100110010101101110&lt;br /&gt;
Wenn wir diese Folge als eine einzige Gruppe betrachten und als positive ganze Zahl in Binärdarstellung (unsigned integer) interpretieren, ergibt sich die Dezimalzahl 4234306926. Interpretieren wir dieselbe Gruppe als vorzeichenbehaftete ganze Zahl in [http://de.wikipedia.org/wiki/Zweierkomplement Zweierkomplement]-Darstellung (signed integer), ergibt sich statt dessen die Dezimalzahl -60660370.&amp;lt;br&amp;gt;&lt;br /&gt;
Alternativ können wir die Folge in vier Gruppen zu 8 Bit gruppieren, und die Gruppen als Zeichencodes im Latin-1 Zeichensatz interpretieren. Wir erhalten die Zeichenkette &amp;quot;üben&amp;quot;:&lt;br /&gt;
 11111100 01100010 01100101 01101110 =&amp;gt; üben&lt;br /&gt;
Interpretieren wir dieselben Gruppen hingegen als Farbe im RGBA System, erhalten wir ein halbtransparentes Rosa (Rot: 252, Grün: 98, Blau: 101, Alpha: 110).&amp;lt;br&amp;gt;&lt;br /&gt;
Eine weitere Interpretation ist diejenige als 32-Bit Gleitkommazahl gemäß [http://en.wikipedia.org/wiki/IEEE_floating-point_standard IEEE Standard 754] (float). Dabei wird die Folge in Gruppen zu 1 Bit, 8 Bit und 23 Bit eingeteilt:&lt;br /&gt;
 1 11111000 11000100110010101101110&lt;br /&gt;
Die Gruppen werden als nicht-negative Binärzahlen gelesen, wobei die erste Gruppe das Vorzeichen &amp;lt;tt&amp;gt;s&amp;lt;/tt&amp;gt; der Gleitkommazahl ist (0 bedeutet &amp;quot;+&amp;quot;, 1 bedeutet &amp;quot;-&amp;quot;), die zweite ist ihr Exponent &amp;lt;tt&amp;gt;exp&amp;lt;/tt&amp;gt; und die dritte die Mantisse &amp;lt;tt&amp;gt;m&amp;lt;/tt&amp;gt; (Hier gilt &amp;lt;tt&amp;gt;s = 1&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;exp = 248&amp;lt;/tt&amp;gt; und &amp;lt;tt&amp;gt;m = 6448494&amp;lt;/tt&amp;gt;). Die Umrechnung in eine Gleitkommazahl erfolgt, gemäß IEEE Standard, nach folgender Formel:&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;tt&amp;gt;z = (1 - 2*s) * 2&amp;lt;sup&amp;gt;exp-127&amp;lt;/sup&amp;gt; * (1 + m * 2&amp;lt;sup&amp;gt;-23&amp;lt;/sup&amp;gt;)&amp;lt;/tt&amp;gt;.&amp;lt;br&amp;gt;&lt;br /&gt;
In Dezimaldarstellung ist dies rund &amp;lt;tt&amp;gt;-4.7020653*10&amp;lt;sup&amp;gt;36&amp;lt;/sup&amp;gt;&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Im Sinne einer hierarchischen Gruppierung können wir jetzt z.B. eine Datenstruktur &amp;quot;Farbbild&amp;quot; definieren, indem wir viele RGBA-Werte zu einem 2-dimensionalen Array zusammenfassen. Eine Datenstruktur &amp;quot;komplexe Zahl&amp;quot; wird durch ein geordnetes Paar von Gleitkommazahlen gebildet, eine &amp;quot;Meßreihe&amp;quot; als Liste von ganzen Zahlen oder Gleitkommawerten (je nach Art der Messung), usw.&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;0&amp;quot; cellspacing=&amp;quot;0&amp;quot; cellpadding=&amp;quot;5&amp;quot; &lt;br /&gt;
|-valign=&amp;quot;top&amp;quot; &lt;br /&gt;
| Die Bedeutung der einzelnen Gruppen ist dem Computer normalerweise nicht explizit bekannt. Vielmehr wird sie implizit durch die ''Menge der darauf ausführbaren Operationen'' definiert. Man bezeichnet die Verbindung einer Datenrepräsentation mit einer Menge von erlaubten Operationen als '''(Daten-)Typ''' oder als '''Klasse'''. Man kann sich die drei Möglichkeiten &amp;quot;Gruppierung von Daten&amp;quot;, &amp;quot;Bedeutung der Gruppen&amp;quot; und &amp;quot;Menge der darauf ausführbaren Operatoren&amp;quot; als Ecken eines Dreiecks vorstellen. Definiert man zwei Ecken des Dreiecks, ist auch die dritte weitgehend festgelegt. Offensichtlich gibt es dafür drei Möglichkeiten: Legt man &amp;quot;Gruppierung&amp;quot; und &amp;quot;Bedeutung&amp;quot; fest, erhalten wir eine Datenstruktur, und bei &amp;quot;Gruppierung&amp;quot; plus &amp;quot;Operatoren&amp;quot; einen Klasse bzw. einen Typ. Die dritte Möglichkeit ist die Defnition der Menge der Operatoren und der Bedeutung. In diesem Fall hat man für die Festlegung der (internen) Gruppierung der Daten, also für die Implementation, noch verhältnismäßig viel Freiheit. Man bezeichnet diese Möglichkeit deshalb als '''Abstrakte Datentypen''' (ADTs). Wir kommen im Kapitel [[Generizität]] auf ADTs zurück.&lt;br /&gt;
| [[Image:Dt dreieck.png]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Programmiersprachen, die ausgereifte Mechanismen zur Definition von Klassen bieten, werden als ''objekt-orientiert'' bezeichnet. Sprachen heißen ''streng typisiert'', wenn der Compiler bzw. Interpreter der Sprache sicherstellt, dass auf jeder Datenstruktur nur die jeweils explizit erlaubten Operationen ausgeführt werden (jeder Versuch, eine illegale Operation auszuführen, wird hier als Fehler signalisiert). Erfolgt diese Prüfung während der Compilierung (also während der Übersetzung des Quellcodes in eine Maschinensprache), spricht man von einer ''statisch typisierten Sprache''. Wird die Prüfung hingegen während der Ausführung des Programms durchgeführt, handelt es sich um eine ''dynamisch typisierte Sprache''. Python ist eine dynamisch-typisierte, objekt-orientierte Sprache. Streng typisiert ist sie allerdings nur für die vordefinierten Klassen. Bei benutzerdefinierten Klassen gibt es (wie bei den meisten anderen Programmiersprachen auch) Möglichkeiten, die erlaubten Operationen zu umgehen. Dies sollte man allerdings nur dann tun, wenn es einen wichtigen Grund gibt. Solange man sich nämlich auf die erlaubten Operationen beschränkt, ist eine große Menge von Fehlerquellen von vornherein ausgeschlossen. &lt;br /&gt;
&lt;br /&gt;
Ein bestimmter Speicherbereich, der den Anforderungen an eine Klasse genügt (wo also die Bits in entsprechender Weise gruppiert und interpretiert werden), wird als '''Objekt''' dieser Klasse oder als '''Instanz''' bezeichnet. Jede Instanz hat eine eindeutige Identität, einen ''Schlüssel''. Innerhalb eines Programms wird dafür gewöhnlich die Speicheradresse des ersten Bytes der Instanz (also der Index der ersten Speicherzelle) verwendet. Dies ist besonders effizient, weil die Speicheradresse für jedes Objekt eindeutig und leicht feststellbar ist. Ist das Objekt hingegen als Datei gespeichert, benötigt man einen expliziten Schlüssel, z.B. den Dateinamen oder die URL. &lt;br /&gt;
&lt;br /&gt;
Das Bitmuster selbst bzw. die daraus folgende Interpretation wird als '''Zustand''' oder '''Wert''' der Instanz bezeichnet. Daraus folgt, dass verschiedene Instanzen einer Klasse dennoch gleiche Werte haben können. Die Menge aller legalen Werte bilden den ''Wertebereich'' der Klasse. Werden Instanzen ausschließlich mit den explizit erlaubten Operationen ihrer Klasse manipuliert, können niemals illegale Werte entstehen. Es liegt auf der Hand, dass illegale Werte schwerwiegende Programmfehler darstellen, die man auf diese Weise vermeidet. [Computerviren tun genau das Gegenteil: Sie verwenden absichtlich verbotene Operationen, um dass Programm in einen illegalen, vom Angreifer gewünschten Zustand zu bringen. Dies ist möglich, weil nicht alle verbotenen Operationen automatisch als Fehler erkannt werden, siehe oben.]&lt;br /&gt;
&lt;br /&gt;
Die meisten Programmiersprachen haben einen oder mehrere spezielle Typen für das Speichern von Objektschlüsseln. Die gebräuchlichsten Namen für diese Typen sind ''Zeiger'' (pointer), ''Referenz'' (reference) und ''Handle''. Wir verwenden das Wort '''Referenz'''. Ein Objekt der Klasse Referenz enthält also den Schlüssel eines anderen Objekts. Man sagt, dass die Referenz ''auf das andere Objekt verweist''. Diese Art der Indirektion ist uns heutzutage durch das Internet bestens vertraut: Jede WWW-Seite ist ein Objekt, und seine URL ist der dazugehörige Schlüssel. Hyperlinks und Lesezeichen (bookmarks) hingegen sind Referenzen, die mittels der URL auf andere Seiten verweisen.&lt;br /&gt;
&lt;br /&gt;
Aus der Unterscheidung von Werten und Referenzen ergibt sich die wichtige Unterscheidung von ''Wertsemantik'' und ''Referenzsemantik''. Wird nämlich ein Objekt an eine Variable zugewiesen&lt;br /&gt;
 x = anObject&lt;br /&gt;
so hängt die korrekte Verwendung der Variablen &amp;lt;tt&amp;gt;x&amp;lt;/tt&amp;gt; davon ab, ob sie das Objekt in Form eines Wertes oder einer Referenz speichert. Im ersten Fall wird das Objekt selbst kopiert, und es entsteht ein neues Objekt mit neuer Identität, aber gleichem Zustand. Im andern Fall wird nur der Schlüssel kopiert, und die Referenz verweist nach wie vor auf das ursprüngliche Objekt. Ist &amp;lt;tt&amp;gt;x&amp;lt;/tt&amp;gt; ein Wert, so verändert eine Manipulation von &amp;lt;tt&amp;gt;x&amp;lt;/tt&amp;gt; nur das neue Objekt (das ursprüngliche bleibt erhalten). Ist &amp;lt;tt&amp;gt;x&amp;lt;/tt&amp;gt; hingegen eine Referenz, wird immer das ürsprüngliche Objekt manipuliert (denn es gibt ja keine Kopie). Ob eine Variable einen Wert oder eine Referenz enthält, wird in jeder Programmiersprache anderes festgelegt. In Python gilt&lt;br /&gt;
* Zahlen (Typen &amp;lt;tt&amp;gt;bool&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;int&amp;lt;/tt&amp;gt;, und &amp;lt;tt&amp;gt;float&amp;lt;/tt&amp;gt;) werden immer als Werte gespeichert und kopiert.&lt;br /&gt;
* Alle anderen Typen werden als Referenzen gespeichert und kopiert.&lt;br /&gt;
* Für alle Typen kann Wertsemantik mit Hilfe des Python-Moduls [http://docs.python.org/lib/module-copy.html copy] erzwungen werden.&lt;br /&gt;
Das Verständnis von Werten und Referenzen wird in der 1. Übung vertieft.&lt;br /&gt;
&lt;br /&gt;
Der Entwurf von Datentypen wird uns im Laufe der Vorlesung immer wieder beschäftigen.&lt;br /&gt;
&lt;br /&gt;
== Fundamentale Algorithmen ==&lt;br /&gt;
&lt;br /&gt;
Einige Algorithmen werden praktisch bei jeder Klasse benötigt, unabhängig vom eigentlichem Verwendungszweck der Klasse. Es ist wichtig, diese fundamentalen Algorithmen zu kennen. Außerdem eignen sie sich gut zur Einführung der Grundprinzipien der Algorithmen-Spezifikation mittels Vor- und Nachbedingungen. Diese Bedingungen beschreiben Eigenschaften, die die Variablen des Systems ''vor'' bzw. ''nach'' der Ausführung des Algorithmus haben sollen. Damit man außerdem die Veränderungen durch den Algorithmus beschreiben kann, führt man zu jeder Variablen (z.B. &amp;lt;tt&amp;gt;x&amp;lt;/tt&amp;gt;) eine Hilfsvariable (z.B. &amp;lt;tt&amp;gt;x&amp;lt;sub&amp;gt;o&amp;lt;/sub&amp;gt;&amp;lt;/tt&amp;gt;, sprich &amp;quot;x-old&amp;quot;) ein. In den Hilfsvariablen wird der Zustand ''vor'' der Ausführung des Algorithmus gespeichert, so dass man diesen noch abfragen kann, wenn Variablen durch den Algorithmus verändert werden. Wenn der Algorithmus beispielsweise die Variable &amp;lt;tt&amp;gt;x&amp;lt;/tt&amp;gt; inkrementiert (um eins erhöht), gilt die Nachbedingung &amp;lt;tt&amp;gt;x == x&amp;lt;sub&amp;gt;o&amp;lt;/sub&amp;gt; + 1&amp;lt;/tt&amp;gt; (darin ist &amp;lt;tt&amp;gt;x&amp;lt;/tt&amp;gt; der neue, und &amp;lt;tt&amp;gt;x&amp;lt;sub&amp;gt;o&amp;lt;/sub&amp;gt;&amp;lt;/tt&amp;gt; der alte Wert der Variablen). Falls &amp;lt;tt&amp;gt;x&amp;lt;/tt&amp;gt; hingegen nicht verändert wird, gilt &amp;lt;tt&amp;gt;x == x&amp;lt;sub&amp;gt;o&amp;lt;/sub&amp;gt;&amp;lt;/tt&amp;gt;. (Man beachte, dass dies in der Literatur nicht einheitlich gehandhabt wird -- einige Autoren verwenden z.B. &amp;lt;tt&amp;gt;x&amp;lt;/tt&amp;gt; für den Zustand vor Ausführung des Algorithmus, und &amp;lt;tt&amp;gt;x'&amp;lt;/tt&amp;gt; für denjenigen danach. Diese Syntax ist jedoch mit den meisten Programmiersprachen inkompatibel.)&lt;br /&gt;
&lt;br /&gt;
Die wichtigste Gruppe von fundamentalen Funktionen sind die '''Konstruktoren''', die einen vorher unbenutzten Speicherbereich in eine Datenstruktur mit einem wohldefinierten Anfangswert transformieren. In Python haben die Konstruktoren im allgemeinen den gleichen Namen wie die dazugehörige Klasse, also z.B.&lt;br /&gt;
 i = int()   # erzeuge eine ganze Zahl mit Anfangswert 0&lt;br /&gt;
 f = float() # erzeuge eine Gleitkommazahl mit Anfangswert 0&lt;br /&gt;
 a = list()  # erzeuge ein leeres Array&lt;br /&gt;
usw. (Man beachte, dass das Python-Array den Klassennamen &amp;lt;tt&amp;gt;list&amp;lt;/tt&amp;gt; hat. Dies hat nichts mit verketteten Listen zu tun.) Konstruktoren ohne Argumente bezeichnet man als ''Standard-Konstruktoren'' (default constructors). Ja nach Typ gibt es meist noch weitere Konstruktoren, die Objekte mit anderen Anfangswerten erzeugen, z.B.&lt;br /&gt;
 i = int(2)     # erzeuge eine ganze Zahl mit Anfangswert 2&lt;br /&gt;
 i = 2          # ebenso (abgekürzte Schreibweise)&lt;br /&gt;
 f = float(1.5) # erzeuge eine Gleitkommazahl mit Anfangswert 1.5&lt;br /&gt;
 f = 1.5        # ebenso (abgekürzte Schreibweise)&lt;br /&gt;
 a = [i, f]     # erzeuge ein Array mit Kopien der Werte von i und f&lt;br /&gt;
(Das Array &amp;lt;tt&amp;gt;a&amp;lt;/tt&amp;gt; enthält Kopien der Werte, weil Zahlen immer mit Wertsemantik zugewiesen werden.) Die allgemeine Spezifikation eines Standard-Konstruktors lautet&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\begin{array}{ll}&lt;br /&gt;
\mathrm{Precondition: } &amp;amp; T \in \mathrm{Types}\\&lt;br /&gt;
\mathrm{Constructor: } &amp;amp; t = T() \\&lt;br /&gt;
\mathrm{Postcondition: } &amp;amp; t \in T&lt;br /&gt;
\end{array}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Der Ausdruck &amp;lt;math&amp;gt;t \in T&amp;lt;/math&amp;gt; besagt, dass t nach Ausführung des Konstruktors eine legale Instanz des Typs T (oder eine Referenz auf einen solche Instanz) sein muss. In Pythonsyntax kann dies folgendermassen geschrieben werden&lt;br /&gt;
 import inspect           # wir brauchen das inspect-Modul&lt;br /&gt;
 &lt;br /&gt;
 if inspect.isclass(T):   # prüfe, dass T ein Type ist&lt;br /&gt;
      t = T()&lt;br /&gt;
 assert isinstance(t, T)&lt;br /&gt;
Natürlich funktioniert der Code nur, wenn die Klasse &amp;lt;tt&amp;gt;T&amp;lt;/tt&amp;gt; tatsächlich existiert und dafür ein Standardkonstruktor definiert wurde. Das Gegenstück zu Konstruktoren sind die '''Destruktoren''', die den Speicher der Datenstruktur wieder frei geben. Da Python automatisches Speichermanagment unterstützt, werden die Destruktoren automatisch aufgerufen. Wir können sie deshalb hier übergehen.&lt;br /&gt;
&lt;br /&gt;
Sehr wichtig sind auch die '''Vergleichsoperatoren'''. Wir müssen dabei unterscheiden, ob auf Gleichheit der Referenzen (''identity'') oder auf Gleichkeit der Werte (''equality'') geprüft werden soll. In Python werden dazu die Operatoren &amp;lt;tt&amp;gt;is&amp;lt;/tt&amp;gt; bzw. &amp;lt;tt&amp;gt;==&amp;lt;/tt&amp;gt; verwendet. Die Negation erhält man durch &amp;lt;tt&amp;gt;is not&amp;lt;/tt&amp;gt; bzw. &lt;br /&gt;
&amp;lt;tt&amp;gt;!=&amp;lt;/tt&amp;gt;&lt;br /&gt;
 a = [1, 2]&lt;br /&gt;
 b = [1, 2]&lt;br /&gt;
 &lt;br /&gt;
 a == b      # True  weil gleiche Werte&lt;br /&gt;
 a != b      # False weil Negation&lt;br /&gt;
 a is b      # False weil unterschiedliche Identität&lt;br /&gt;
 a is not b  # True  weil Negation&lt;br /&gt;
&lt;br /&gt;
(Beachte: beim Vergleich von Zahlen des gleichen Typs liefern &amp;lt;tt&amp;gt;is&amp;lt;/tt&amp;gt; und &amp;lt;tt&amp;gt;==&amp;lt;/tt&amp;gt; immer dasselbe Ergebnis.) Natürlich impliziert die Gleichheit der Schlüssel (Identität der Objekte) die Gleichheit der Werte.&lt;br /&gt;
&lt;br /&gt;
Ebenso wichtig sind die '''Zuweisungen'''. Hier zeigt sich besonders der Unterschied zwischen Wert- und Referenzsemantik. Im Falle von Wertsemantik gilt&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\begin{array}{ll}&lt;br /&gt;
\mathrm{Preconditions: } &amp;amp; s,t \in T \\&lt;br /&gt;
                         &amp;amp; s \mathrm{\ is\ not\ } t \\&lt;br /&gt;
\mathrm{Assign\ by\ value: } &amp;amp; s = t \\&lt;br /&gt;
\mathrm{Postconditions: } &amp;amp; t \mathrm{\ is\ } t_o \\&lt;br /&gt;
                          &amp;amp; s \mathrm{\ is\ not\ } t \\&lt;br /&gt;
                          &amp;amp; s == t &lt;br /&gt;
\end{array}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Das heisst, t darf sich nicht verändern, und s hat nach der Zuweisung den gleichen Wert wie t. Bei Referenzsemantik gilt sogar&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\begin{array}{ll}&lt;br /&gt;
\mathrm{Precondition: } &amp;amp; t \in T \\&lt;br /&gt;
\mathrm{Assign\ by\ reference: } &amp;amp; s = t \\&lt;br /&gt;
\mathrm{Postconditions: } &amp;amp; t \mathrm{\ is\ } t_o \\&lt;br /&gt;
                          &amp;amp; s \mathrm{\ is\ } t&lt;br /&gt;
\end{array}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Dies entspricht dem Pythoncode&lt;br /&gt;
 x = y&lt;br /&gt;
 assert x is y&lt;br /&gt;
Die Wertsemantik muss man in Python explizit erzwingen&lt;br /&gt;
 import copy  # wir brauchen das copy-Modul&lt;br /&gt;
 &lt;br /&gt;
 x = copy.deepcopy(y)&lt;br /&gt;
 assert x == y&lt;br /&gt;
 assert x is not y&lt;br /&gt;
&lt;br /&gt;
Mit der Zuweisung eng verwandt ist die Funktion &amp;lt;tt&amp;gt;swap&amp;lt;/tt&amp;gt;, die den Inhalt von zwei Variablen vertauscht:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\begin{array}{ll}&lt;br /&gt;
\mathrm{Precondition: } &amp;amp; t \in T, s \in S \\&lt;br /&gt;
\mathrm{Algorithm\ swap: } &amp;amp; \mathrm{swap}(s, t) \\&lt;br /&gt;
\mathrm{Postconditions: } &amp;amp; t \mathrm{\ is\ } s_o \\&lt;br /&gt;
                          &amp;amp; s \mathrm{\ is\ } t_o&lt;br /&gt;
\end{array}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Diese Funktion wird sich beim Sortieren als sehr nützlich erweisen, weil dort das Vertauschen von zwei Datenelementen eine Grundoperation ist. In Python kann man dies so implementieren:&lt;br /&gt;
 t, s = s, t  # swap&lt;br /&gt;
Dabei macht man sich zunutze, dass Python mehrere Variablen in einem einzigen Statement zuweisen kann.&lt;/div&gt;</summary>
		<author><name>Jschleic</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Iteration_versus_Rekursion&amp;diff=2503</id>
		<title>Iteration versus Rekursion</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Iteration_versus_Rekursion&amp;diff=2503"/>
				<updated>2008-07-21T11:08:37Z</updated>
		
		<summary type="html">&lt;p&gt;Jschleic: /* Version 1: Naive rekursive Implementation */ Grafik eigefügt&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Rekursion in der Informatik:==&lt;br /&gt;
&lt;br /&gt;
Einen Funktion f ist '''rekursiv''' wenn sie sich selbst aufruft.&lt;br /&gt;
Die Funktion heißt ''indirekt-rekursiv'' wenn sie sich selbst aufruft über den Umweg einer anderen Funktion (oder mehrerer anderer Funktionen).&lt;br /&gt;
&lt;br /&gt;
Beispiel einer indirekten Rekursion:&lt;br /&gt;
 &lt;br /&gt;
    '''foo'''  ---&amp;gt;  '''bar'''  ---&amp;gt;  '''foo'''&lt;br /&gt;
       ruft auf    ruft auf&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Entscheidende Eigenschaften der Rekursion===&lt;br /&gt;
&lt;br /&gt;
* Jeder Aufruf einer rekursiven Funktion f hat sein &amp;lt;u&amp;gt;eigenes&amp;lt;/u&amp;gt; Set von lokalen Variablen, d.h. der rekursive Aufruf hat die gleichen Anweisungen auszuführen, aber er hat seine eigenen Daten. Betrachten wir z.B. die rekursive Funktion&lt;br /&gt;
      def f(n):&lt;br /&gt;
          r = f(n-1)&lt;br /&gt;
          ... # weiterer Code&lt;br /&gt;
          return r&lt;br /&gt;
&lt;br /&gt;
:Für ein gegebenes n erhalten wir die Aufrufkette:&lt;br /&gt;
      f(n)             1.Ebene&lt;br /&gt;
         f(n-1)        2.Ebene&lt;br /&gt;
              f(n-2)   3.Ebene&lt;br /&gt;
                       ...&lt;br /&gt;
:Die Funktionsaufrufe der verschiedenen Ebenen werden so ausgeführt, als ob wir für jede Ebene eine eigene Funktion f&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;(n&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;), f&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(n&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;), f&amp;lt;sub&amp;gt;3&amp;lt;/sub&amp;gt;(n&amp;lt;sub&amp;gt;3&amp;lt;/sub&amp;gt;) usw. definiert hätten:&lt;br /&gt;
&lt;br /&gt;
      f&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;(n&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;)            1.Ebene ---&amp;gt; n&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; = n&lt;br /&gt;
          f&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(n&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;)        2.Ebene ---&amp;gt; n&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; = n&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;-1&lt;br /&gt;
              f&amp;lt;sub&amp;gt;3&amp;lt;/sub&amp;gt;(n&amp;lt;sub&amp;gt;3&amp;lt;/sub&amp;gt;)    3.Ebene ---&amp;gt; n&amp;lt;sub&amp;gt;3&amp;lt;/sub&amp;gt; = n&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;-1 = n&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;-2 = n-2&lt;br /&gt;
                        ...&lt;br /&gt;
:Die Funktionen f&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;(n&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;), f&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(n&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;), f&amp;lt;sub&amp;gt;3&amp;lt;/sub&amp;gt;(n&amp;lt;sub&amp;gt;3&amp;lt;/sub&amp;gt;) enthalten alle den gleichen Code, aber mit unterschiedlich benannten Variablen n&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;, n&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;, n&amp;lt;sub&amp;gt;3&amp;lt;/sub&amp;gt;. Durch diese Umbenennungen sollte die obige Aussage deutlich werden.&lt;br /&gt;
&lt;br /&gt;
* Jede rekursive Funktion muß &amp;lt;u&amp;gt;mindestens '''einen''' nicht-rekursiven&amp;lt;/u&amp;gt; Zweig haben. Dieser Zweig wird als '''Basisfall''' oder '''Rekursionsabschluss''' bezeichnet.&lt;br /&gt;
&lt;br /&gt;
* Jeder rekursive Aufruf muß über &amp;lt;u&amp;gt;endlich viele Stufen&amp;lt;/u&amp;gt; auf einen Basisfall zurückgeführt werden, denn sonst erhält man eine Endlosrekursion (und das ist fast so übel wie eine Endlosschleife -- &amp;quot;fast&amp;quot; deshalb, weil die Endlosrekursion spätestens dann abbricht wenn der Speicher voll ist --&amp;gt; siehe Übung 7 Aufgabe 1).&lt;br /&gt;
&lt;br /&gt;
* Die Anzahl der rekursiven Aufrufe bis zum Rekursionsabschluss bezeichnet man als '''Rekursionstiefe d'''.&lt;br /&gt;
&lt;br /&gt;
* Jede rekursive Funktion kann in eine iterative Funktion umgewandelt werden, die statt rekursiver Aufrufe eine Schleife enthält. Wir beschreiben dies unten anhand von Beispielen.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Arten der Rekursion===&lt;br /&gt;
&lt;br /&gt;
Die Arten der Rekursion sind hier nicht vollständig angegeben, es gibt noch weitere. Aber die hier folgenden sind für die Programmierung am wichtigsten.&lt;br /&gt;
&lt;br /&gt;
====Lineare Rekursion====&lt;br /&gt;
&lt;br /&gt;
* Jeder Ausführungspfad durch die Funktion f enthält &amp;lt;u&amp;gt;höchstens einen&amp;lt;/u&amp;gt; rekursiven Aufruf.&lt;br /&gt;
* Höchstens einen, weil mindestens ein Pfad existieren muss, der keinen rekursiven Aufruf enthält (der Basisfall).                                  &lt;br /&gt;
:&amp;amp;rArr; Das Ergebnis der Aufrufe ist eine 1-dimensionale &amp;quot;Rekursionskette&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
====Baumrekursion====&lt;br /&gt;
(auch Kaskadenrekursion)&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Mindestens ein Ausführungspfad durch die Funktion enthält mindestens 2 rekursive Aufrufe.&lt;br /&gt;
* Baumrekursion ist damit das Gegenteil der linearen Rekursion.&lt;br /&gt;
:&amp;amp;rArr; Das Ergebnis der Aufrufe ist ein verzweigter &amp;quot;Rekursionsbaum&amp;quot;. Ein baum-rekursiver Algorithmus ist deshalb häufig ineffizient: Wird der Pfad mit mehreren rekursiven Aufrufen mindestens &amp;amp;Omega;(d)-mal ausgeführt (wobei d die Rekursionstiefe) ist, entsteht ein Rekursionsbaum mit O(2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt;) Knoten, also ein Algorithmus mit exponentieller Komplexität.&lt;br /&gt;
&lt;br /&gt;
====Course-of-values recursion====&lt;br /&gt;
&lt;br /&gt;
* Das Ergebnis des rekursiven Aufrufes für Argument n hängt nur von den Ergebnissen der letzten p-Aufrufe, also für Argument n-1,n-2,...,n-p ab (wobei p eine Konstante ist).&lt;br /&gt;
&lt;br /&gt;
====Primitive Rekursion====&lt;br /&gt;
&lt;br /&gt;
* Spezialfall der course-of-values recursion mit p=1.&lt;br /&gt;
:&amp;amp;rArr;  Das ist auch ein Spezialfall der linearen Rekursion, denn wenn das Ergebnis nur von einem rekursiven Aufruf abhängt, kann kein Rekursionsbaum entstehen.&lt;br /&gt;
&lt;br /&gt;
====Endrekursion====&lt;br /&gt;
&lt;br /&gt;
* In jedem rekursiven Ausführungspfad ist der rekursive Aufruf der letzte Befehl vor dem return-Befehl.&lt;br /&gt;
:&amp;amp;rArr;  Das ist ein Spezialfall der linearen Rekursion, denn es kann nur einen letzten Befehl vor jedem return geben, ein zweiter rekursiver Aufruf könnte allenfalls der vorletzte Befehl sein.&lt;br /&gt;
&lt;br /&gt;
==Beispiele und Umwandlung in Iteration==&lt;br /&gt;
&lt;br /&gt;
===Beispiel für alle Rekursionsarten: Fibonacci-Zahlen===&lt;br /&gt;
&lt;br /&gt;
Wir verdeutlichen die verschiedenen Rekursionsarten und ihre Umwandlung in iterative Algorithmen am Beispiel der Fibonacci-Zahlen.&lt;br /&gt;
&lt;br /&gt;
;Fibonacci-Zahlen: Die n-te Fibonacci-Zahl ist gemäß der folgenden Rekursionsformel definiert. &lt;br /&gt;
:::f&amp;lt;sub&amp;gt;0&amp;lt;/sub&amp;gt;=0&lt;br /&gt;
:::f&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;=1&lt;br /&gt;
:::f&amp;lt;sub&amp;gt;n&amp;lt;/sub&amp;gt;=f&amp;lt;sub&amp;gt;n-1&amp;lt;/sub&amp;gt;+f&amp;lt;sub&amp;gt;n-2&amp;lt;/sub&amp;gt;&lt;br /&gt;
:Es ergibt sich die Folge: &lt;br /&gt;
:::f&amp;lt;sub&amp;gt;k&amp;lt;/sub&amp;gt;=0, 1, 1, 2, 3, 5, 8, 13,....., (&amp;amp;rArr; Die Reihe wächst exponentiell an.)&lt;br /&gt;
&lt;br /&gt;
Im folgenden zeigen wir 5 verschiedene Algorithmen zu Berechnung der n-ten Fibonacci-Zahl:&lt;br /&gt;
&lt;br /&gt;
====Version 1: Naive rekursive Implementation====&lt;br /&gt;
&lt;br /&gt;
Implementiert man einfach die rekursive Formel der Definition, erhält man:&lt;br /&gt;
&lt;br /&gt;
 def fib1(n):                      # Funtion berechnet die n-te Fibonacci-Zahl&lt;br /&gt;
     if n &amp;lt;= 1: &lt;br /&gt;
          return n                 # Rekursionsabschluss&lt;br /&gt;
     return fib1(n-1) + fib1(n-2)  # Baumrekursion&lt;br /&gt;
&lt;br /&gt;
Die Funktion fib1(n) ist ein Beispiel für eine Baumrekursion mit exponentieller Komplexität.&lt;br /&gt;
Der Aufrufbaum sieht dann wie folgt aus:&lt;br /&gt;
[[Image:Fibrek.png]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--&lt;br /&gt;
                                               fib1(n)&lt;br /&gt;
                                              /       \&lt;br /&gt;
                                             /         \&lt;br /&gt;
                                            /           \&lt;br /&gt;
                                           /             \&lt;br /&gt;
                                    fib1(n-1)           fib1(n-2)&lt;br /&gt;
                                    /       \           /       \&lt;br /&gt;
                                   /         \         /         \&lt;br /&gt;
                                  /           \       /           \&lt;br /&gt;
                              fib1(n-2)  fib1(n-3)  fib1(n-3)   fib1(n-4)&lt;br /&gt;
--&amp;gt;&lt;br /&gt;
Im Falle der Fibonacci-Zahlen ist dies ein ungünstiger Algorithmus, weil viele Teilergebnise wiederholt berechnet werden (z.B. f&amp;lt;sub&amp;gt;n-2&amp;lt;/sub&amp;gt; und f&amp;lt;sub&amp;gt;n-3&amp;lt;/sub&amp;gt;).&lt;br /&gt;
Aber einige Probleme sind &amp;lt;u&amp;gt;echt&amp;lt;/u&amp;gt; Baumrekursiv, z.B. das Ausrechnen der möglichen Züge beim Schachspiel.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
;Es gilt folgender grundlegender Satz:&lt;br /&gt;
Jeder rekursiver Algorithmus kann mit Hilfe eines Stacks in einen iterativen Algorithmus umgewandelt werden (d.h. mit einer Schleife statt einer Rekursion). Das bedeutet, dass rekursive Programme gleichmächtig sind wie Programme mit Schleifen (z.B. WHILE-Programme, siehe [[Einführung#Zur Frage der elementaren Schritte|erste Vorlesung]]), d.h. gleichmächtig wie die Turing-Maschine. Die Komplexität des Algorithmus ändert sich durch die Umwandlung nicht.&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Version 2: Umwandlung in einen iterativen Algorithmus mit Stack====&lt;br /&gt;
&lt;br /&gt;
 def fib2(n):&lt;br /&gt;
     stack = [n]   # der Stack enthält als erstes Teilproblem das ursprüngliche Problem &amp;quot;n-te Fibonacci-Zahl berechnen&amp;quot;&lt;br /&gt;
     f = 0         # f wird später die Lösung enthalten&lt;br /&gt;
     &lt;br /&gt;
     while len(stack) &amp;gt; 0:          # solange noch was auf dem Stack liegt, ist noch Arbeit zu tun&lt;br /&gt;
           k = stack.pop()          # Teilproblem von Stack entnehmen und lösen&lt;br /&gt;
           if k &amp;lt;= 1:               &lt;br /&gt;
                f += k              # entweder: Teilergebnis zur Lösung addieren (das war vorher der Rekursionsabschluss)&lt;br /&gt;
           else:                    &lt;br /&gt;
                stack.append(k-1)   # oder: zwei neue Teilprobleme auf dem Stack ablegen &lt;br /&gt;
                stack.append(k-2)   # (das waren vorher die rekursiven Aufrufe)&lt;br /&gt;
     return f&lt;br /&gt;
&lt;br /&gt;
====Version 3: Course-of-values recursion====&lt;br /&gt;
&lt;br /&gt;
Wie man unmittelbar aus der Definitione erkennt, ist die Berechnung der Fibonacci-Zahlen Course-of-values rekursiv mit p=2. Eine entsprechende Implementation verwendet eine Hilfsunfktion, die immer die Ergebnisse der p letzten Aufrufe zurückgibt:&lt;br /&gt;
&lt;br /&gt;
 def fib3(n):&lt;br /&gt;
     if n == 0: return 0     # Rekursionsabschluss&lt;br /&gt;
     f&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;,f&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; = fib3Impl(n)     # Hilfsfuntion, f&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; ist die Fibonacci-Zahl von n und f&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; ist dei Fibonacci-Zahl von (n-1)&lt;br /&gt;
     return f&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
Die Hilfsfunktion enthält jetzt die eigentliche Rekursion. Sie berechnet die Fibonacci-Zahlen f&amp;lt;sub&amp;gt;k&amp;lt;/sub&amp;gt; und f&amp;lt;sub&amp;gt;k-1&amp;lt;/sub&amp;gt; aus den Zahlen f&amp;lt;sub&amp;gt;k-1&amp;lt;/sub&amp;gt; und f&amp;lt;sub&amp;gt;k-2&amp;lt;/sub&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
 def fib3Impl(n):&lt;br /&gt;
     if n == 1: return 1, 0         # gebe die Fibonacci-Zahl von 1 und die davor zurück&lt;br /&gt;
     else:                          # rekursiver Aufruf&lt;br /&gt;
        f&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;,f&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; = fib3Impl(n-1)       # f&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; ist Fibonacci-Zahl von (n-1), f&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; die von (n-2)&lt;br /&gt;
        return f&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; + f&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;, f&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;          # gebe neue Fibonacci-Zahl f&amp;lt;sub&amp;gt;n&amp;lt;/sub&amp;gt;=f&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;+f&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; und die vorherige (f&amp;lt;sub&amp;gt;n-1&amp;lt;/sub&amp;gt;=f&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;) zurück.&lt;br /&gt;
&lt;br /&gt;
&amp;amp;rArr; Diese Implementation ist jetzt linear-rekursiv (aber nicht endrekursiv). Sie hat damit lineare Komplexität und nicht exponentielle wie die beiden vorherigen Versionen.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
;Es gilt folgender Satz:&lt;br /&gt;
Jede Course-of-values Rekursion kann in Endrekursion umgewandelt werden.&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Version 4: Endrekursiv====&lt;br /&gt;
&lt;br /&gt;
Man gelangt von der Course-of-values recursion zur Endrekursion, indem man einfach die Berechnungsreihenfolge umkehrt: statt rückwärts von f&amp;lt;sub&amp;gt;n&amp;lt;/sub&amp;gt; nach f&amp;lt;sub&amp;gt;0&amp;lt;/sub&amp;gt; zu arbeiten, arbeitet man vorwärts von f&amp;lt;sub&amp;gt;0&amp;lt;/sub&amp;gt; nach f&amp;lt;sub&amp;gt;n&amp;lt;/sub&amp;gt;. Dazu muss der Hilfsfunktion eine Zählvariable übergeben werden, die angibt, wie viele Schritte noch bis zum Ziel f&amp;lt;sub&amp;gt;n&amp;lt;/sub&amp;gt; verbleiben. Außerdem erhält die Hilfsfunktion die beiden vorherigen Fibonacci-Zahlen als Argument übergeben:&lt;br /&gt;
&lt;br /&gt;
 def fib4(n)&lt;br /&gt;
     if n == 0: &lt;br /&gt;
        return 0    &lt;br /&gt;
     else:                    &lt;br /&gt;
        return fib4Impl(0, 1, n) &lt;br /&gt;
 &lt;br /&gt;
Die Hilfsfunktion:&lt;br /&gt;
&lt;br /&gt;
 def fib4Impl(f&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;, f&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;, counter)&lt;br /&gt;
     if counter == 1: &lt;br /&gt;
        return f&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;&lt;br /&gt;
     else:&lt;br /&gt;
        return fib4Impl(f&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;, f&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;+f&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;, counter-1)   # f&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; ist die vorletzte Fibonacci-Zahl,&lt;br /&gt;
                                                # f&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;+f&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; wird die neue Fibonacci-Zahl und wir müssen counter um 1 herunterzählen&lt;br /&gt;
&lt;br /&gt;
Beispiel mit n=3:&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;0&amp;quot; cellpadding=&amp;quot;7&amp;quot;&lt;br /&gt;
|-align=&amp;quot;center&amp;quot;  &lt;br /&gt;
|&lt;br /&gt;
! &amp;amp;nbsp;f&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;&amp;amp;nbsp;&lt;br /&gt;
! &amp;amp;nbsp;f&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;&amp;amp;nbsp;&lt;br /&gt;
! counter &lt;br /&gt;
|-  &lt;br /&gt;
| fib4Impl 1.Aufruf &lt;br /&gt;
|align=&amp;quot;center&amp;quot;| 0 &lt;br /&gt;
|align=&amp;quot;center&amp;quot;| 1 &lt;br /&gt;
|align=&amp;quot;left&amp;quot;| 3 &lt;br /&gt;
|-&lt;br /&gt;
| fib4Impl 2.Aufruf &lt;br /&gt;
|align=&amp;quot;center&amp;quot;| 1 &lt;br /&gt;
|align=&amp;quot;center&amp;quot;| 1  &lt;br /&gt;
|align=&amp;quot;left&amp;quot;| 2 &lt;br /&gt;
|-&lt;br /&gt;
| fib4Impl 3.Aufruf &lt;br /&gt;
|align=&amp;quot;center&amp;quot;| 1 &lt;br /&gt;
|align=&amp;quot;center&amp;quot;| 2  &lt;br /&gt;
| 1 &amp;amp;rArr; Rekursionsabschluss &amp;amp;rArr; return 2&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;rArr; Ergebnis von fib4(3) (die 3. Fibonacci-Zahl) ist 2.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
;Es gilt folgender grundlegender Satz:&lt;br /&gt;
Jede endrekursive Funktion kann &amp;lt;u&amp;gt;ohne&amp;lt;/u&amp;gt; Stack in eine iterative Funktion umgewandelt werden. &lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Bei einigen Programmiersprachen (z.B. Lisp, Scheme) wird dies von Compiler sogar automatisch erledigt (als Programmoptimierung, weil Iteration im allgemeinen schneller ist als Rekursion).&lt;br /&gt;
&lt;br /&gt;
====Version 5: Umwandlung in einen iterativen Algorithmus ohne Stack====&lt;br /&gt;
&lt;br /&gt;
 def fib5(n):&lt;br /&gt;
     f&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;, f&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; = 0, 1&lt;br /&gt;
     while n &amp;gt; 0:&lt;br /&gt;
         f&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;, f&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; = f&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;, f&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;+f&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;&lt;br /&gt;
         n -= 1&lt;br /&gt;
     return f&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Das ist genau das gleiche wie &amp;lt;tt&amp;gt;fib4Impl&amp;lt;/tt&amp;gt;. Aber anstelle eines rekursiven Aufrufes werden einfach die Variablen f&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; und f&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; wiederverwendet (mit den neuen Werten überschrieben). Dies ist möglich, weil die originalen Werte nicht mehr benötigt werden, denn der rekursive Aufruf bei &amp;lt;tt&amp;gt;fib4Impl&amp;lt;/tt&amp;gt; war der letzte Befehl vor dem return (Endrekursion!).&lt;br /&gt;
&lt;br /&gt;
===Beispiel für die Umwandlung von Rekursion in Iteration: treeSort===&lt;br /&gt;
&lt;br /&gt;
Input: &lt;br /&gt;
* ein balancierter Binärbaum, repräsentiert durch seinen Wurzelknoten &amp;lt;tt&amp;gt;root&amp;lt;/tt&amp;gt;&lt;br /&gt;
* ein leeres dynamisches Array a, das später die sortierten Elemente des Baums enthalten wird&lt;br /&gt;
Aufgerufen wird: &lt;br /&gt;
&lt;br /&gt;
     treeSort(root,a)  # kopiert Elemente des Baums sortiert in Array a&lt;br /&gt;
&lt;br /&gt;
Wiederholung des rekursiven Algorithmus (vergleiche Abschnitt &amp;quot;[[Suchen#Sortieren mit Hilfe eines selbst-balancierenden Suchbaums|Sortieren mit Hilfe eines selbst-balancierenden Suchbaums]]&amp;quot;):&lt;br /&gt;
&lt;br /&gt;
 def treeSort(node,a):&lt;br /&gt;
    if node is None: &lt;br /&gt;
        return&lt;br /&gt;
    treeSort(node.left,a)&lt;br /&gt;
    a.append(node.key)&lt;br /&gt;
    treeSort(node.right,a)&lt;br /&gt;
&lt;br /&gt;
Dieser Algorithmus ist baumrekursiv, was nicht weiter überrascht, denn die Rekursion dient ja zur Traversierung eines Baumes. In diesem Fall führt Baumrekursion nicht zu exponentieller Komplexität, weil die Tiefe des Baum nur O(log N) ist. Dadurch benötigt &amp;lt;tt&amp;gt;treeSort&amp;lt;/tt&amp;gt; nur O(2&amp;lt;sup&amp;gt;log N&amp;lt;/sup&amp;gt;) = O(N) Schritte zum Auslesen des Baumes (das Füllen das Baumes benötigt allerdings O(N log N) Schritte und dominiert den Algorithmus).&lt;br /&gt;
&lt;br /&gt;
Die Implementation als iterative Funktion erfolgt mit Hilfe eines Stacks und einer Hilfsfunktion &amp;lt;tt&amp;gt;traverseLeft&amp;lt;/tt&amp;gt;, die den Stack füllt:&lt;br /&gt;
&lt;br /&gt;
 def treeSortIterative(root, a):&lt;br /&gt;
    stack = []&lt;br /&gt;
    traverseLeft(root, stack)   # fülle Stack mit den Knoten des linken Teilbaums von root&lt;br /&gt;
    while len(stack) &amp;gt; 0:&lt;br /&gt;
        node = stack.pop()&lt;br /&gt;
        a.append(node.key)&lt;br /&gt;
        traverseLeft(node.right, stack)  # fülle Stack mit den Knoten des linken Teilbaums von node.right&lt;br /&gt;
&lt;br /&gt;
Hilfsfunktion:&lt;br /&gt;
&lt;br /&gt;
 def traverseLeft(node, stack):&lt;br /&gt;
    while node is not None:&lt;br /&gt;
        stack.append(node)&lt;br /&gt;
        node = node.left&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Auflösung rekursiver Gleichungen==&lt;br /&gt;
&lt;br /&gt;
Um die Komplexität eines rekursiven Algorithmus zu berechen, müssen wir die notwendige Schrittzahl für ein Problem der Größe N bestimmen. Die Schrittzahl einer rekursiven Funktion setzt sich zusammen aus den Schritten, die in der Funktion selbst ausgeführt werden, sowie denen, die die verschiednenen rekursiven Aufrufe beitragen. Um dies allgemein auszudrücken, nehmen wir an, dass jeder rekursive Aufruf sich auf ein Teilproblem des originalen Problems bezieht. Die Größe des i-ten Teilproblems sei n/b&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; (wenn wir also auf dem Originalproblem weiterarbeiten, gilt b&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt;=1), und es soll a&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; Teilprobleme dieser Größe geben. Dann wird die gesuchte Schrittzahl durch folgende Formel ausgedrückt: &lt;br /&gt;
&lt;br /&gt;
:::&amp;lt;math&amp;gt;\underbrace{T(n)}_{\mbox{Schrittzahl}}  =  \underbrace{ a_1T\left(\frac{n}{b_1}\right)+a_2T\left(\frac{n}{b_2}\right)+\cdots+a_kT\left(\frac{n}{b_k}\right) }_{\mbox{rekursive Teilprobleme}}&lt;br /&gt;
+\underbrace{f(n)}_{\mbox{nicht-rekursiver Teil}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;u&amp;gt;Bemerkung:&amp;lt;/u&amp;gt;  Im allgemeinen arbeiten die rekursiven Aufrufe auf Teilproblemen ganzzahliger Größe. Daher muss man &amp;lt;math&amp;gt;\frac{n}{b_i}&amp;lt;/math&amp;gt; gegebenenfalls aufrunden &amp;lt;math&amp;gt;\left\lceil\frac{n}{b_i}\right\rceil&amp;lt;/math&amp;gt; oder abrunden &amp;lt;math&amp;gt;\left\lfloor\frac{n}{b_i}\right\rfloor&amp;lt;/math&amp;gt;. Diese Rundungen spielen für die Auflösung der Formeln meist keine Rolle, weil die Rundungsfehler für große n vernachlässigbar sind.&lt;br /&gt;
&lt;br /&gt;
Rekursionsformeln dieses Typs haben wir z.B. im Kapitel [[Sortieren]] bereits behandelt. Hier wollen wir allgemeine Strategien angeben, wie man die rekursive Form dieser Formeln in eine explizite Form (die keine Ausdrücke der Art &amp;lt;math&amp;gt;T\left(\frac{n}{b_i}\right)&amp;lt;/math&amp;gt; mehr enthält) überführt .&lt;br /&gt;
&lt;br /&gt;
===Master-Theorem===&lt;br /&gt;
&lt;br /&gt;
Im Speziallfall k=1 (d.h. alle Unterprobleme haben die gleiche Größe) vereinfacht sich obige Formel zu:&lt;br /&gt;
:::&amp;lt;math&amp;gt;T(n) = a\,T\left(\frac{n}{b}\right)+f(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
Hier gibt es mit dem Master-Theorem eine sehr allgemeine Regel, wie man dies in eine explizite Form bringt. Einen Beweis für das Master-Theorem findet man z.B. bei T. Cormen, C. Leiserson, R.Rivest: &amp;quot;Algorithmen - eine Einführung&amp;quot;. &lt;br /&gt;
&lt;br /&gt;
Wir definieren zunächst den '''Rekursionsexponenten''':&lt;br /&gt;
:::&amp;lt;math&amp;gt;\rho=\log_b (a)&amp;lt;/math&amp;gt;&lt;br /&gt;
Ja nach dem Verhalten des nicht-rekursiven Anteils unterscheiden wir 3 Fälle&lt;br /&gt;
&lt;br /&gt;
====Fall 1:====&lt;br /&gt;
&lt;br /&gt;
Falls die Funktion f(n) sehr effizient ist, so dass für ihre Komplexität gilt&lt;br /&gt;
:::&amp;lt;math&amp;gt;f(n) \in O(n^{\rho-\epsilon}) , \epsilon&amp;gt;0&amp;lt;/math&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
In diesem Fall dominieren die Kosten der Rekursion, und die Komplexität der rekursiven Funktion ergibt sich aus dem Rekursionsexponenten&lt;br /&gt;
:::&amp;lt;math&amp;gt;T(n) =\Theta(n^{\rho})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Fall 2:====&lt;br /&gt;
&lt;br /&gt;
Wenn die Funktion f(n) genauso effizient ist wie die Rekursion, wenn also gilt&lt;br /&gt;
:::&amp;lt;math&amp;gt;f(n) \in \Theta(n^{\rho}) &amp;lt;/math&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
dann addieren sich die Kosten für f(n) und für die Rekursion, und wir erhalten:&lt;br /&gt;
:::&amp;lt;math&amp;gt;T(n) =\Theta(n^{\rho}\cdot\log n)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Fall 3:====&lt;br /&gt;
&lt;br /&gt;
Wenn die Funktion f(n) nicht sehr effizient ist, so dass für ihre Komplexität gilt &lt;br /&gt;
:::&amp;lt;math&amp;gt;f(n) \in \Omega(n^{\rho+\epsilon})&amp;lt;/math&amp;gt;&lt;br /&gt;
kann das Master-Theorem nur dann eine Aussage machen, wenn außerdem gilt&lt;br /&gt;
:::&amp;lt;math&amp;gt;a\,f\left(\frac{n}{b}\right)\le c\,f(n)\,\textrm{mit}\,c&amp;lt;1&amp;lt;/math&amp;gt;&lt;br /&gt;
Jetzt dominieren die Kosten von f(n), und die Komplexität wird &lt;br /&gt;
:::&amp;lt;math&amp;gt;T(n) =\Theta(f(n))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Beispiel: Merge Sort====&lt;br /&gt;
Im Falle von Merge Sort wird das Problem in zwei gleiche Teile zerlegt, die beide rekursiv sortiert werden (es gilt also a=2, b=2). Das Zusammenfügen der beiden Teile erfordert &amp;amp;Theta;(n) Vergleiche. Wir erhalten also die Formel&lt;br /&gt;
:::&amp;lt;math&amp;gt;T(n)  =  \underbrace{ 2\,T\left(\frac{1}{2}\right)}_{\mbox{rekursive Aufrufe von MergeSort}}+\underbrace {\Theta(n)}_{\textrm{Merge}}&amp;lt;/math&amp;gt;&lt;br /&gt;
Für den Rekursionsexponenten erhalten wir&lt;br /&gt;
:::&amp;lt;math&amp;gt;\rho=log_2 2 = 1&amp;lt;/math&amp;gt;&lt;br /&gt;
Mit f(n) &amp;amp;isin; &amp;amp;Theta;(n) = &amp;amp;Theta;(n&amp;lt;sup&amp;gt;&amp;amp;rho;&amp;lt;/sup&amp;gt;) trifft Fall 2 zu, und wir erhalten für die Komplexität von MergeSort das bereits bekannte Ergebnis: &lt;br /&gt;
:::&amp;lt;math&amp;gt;T(n) \in \Theta(n^ \rho \log n) = \Theta(n\,\log n)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Fälle die nicht durch das Master-Theorem abgedeckt sind:====&lt;br /&gt;
* wenn &amp;lt;math&amp;gt;k \ &amp;gt; 1&amp;lt;/math&amp;gt;: rekursive Teilprobleme &amp;lt;u&amp;gt;verschiedener&amp;lt;/u&amp;gt; Grösse&lt;br /&gt;
* wenn &amp;lt;math&amp;gt;f(n) \in O\left(\frac{n^\rho}{\log n}\right)&amp;lt;/math&amp;gt;: Die Komplexität von f(n) liegt genau zwischen den Fällen 1 und 2.&lt;br /&gt;
* wenn &amp;lt;math&amp;gt;f(n) \in \Omega\left(n^\rho \log n\right)&amp;lt;/math&amp;gt;: Die Komplexität von f(n) liegt genau zwischen den Fällen 2 und 3.&lt;br /&gt;
* wenn für alle c&amp;lt;1 gilt &amp;lt;math&amp;gt;a\,f\left(\frac{n}{b}\right)&amp;gt; c\,f(n)&amp;lt;/math&amp;gt;: Die Komplexität der Funktion f(n) auf den reduzierten Problemen ist zu groß.&lt;br /&gt;
&lt;br /&gt;
===Rekursionsbäume und Substitutionsmethode===&lt;br /&gt;
&lt;br /&gt;
Wenn das Master-Theorem nicht anwendbar ist, muss man die Rekursionsformel selbst auflösen. Dies gilt zum Beispiel, wenn der Algorithmus das Problem in zwei ungleich große Teile zerlegt. Wir betrachten das folgende Beispiel, bei dem das Problem in Teile der Größe 1/3 und 2/3 zerlegt wird und das Zusammenfügen der Teile c*n Schritte erfordert:&lt;br /&gt;
:::&amp;lt;math&amp;gt;T(n)  =  \underbrace{ T\left(\frac{n}{3}\right)}_{\frac{1}{3}\mbox{ der Daten}}+\underbrace {T\left(\frac{2n}{3}\right)}_{\frac{2}{3}\mbox{ der Daten}} +\underbrace {c\cdot n}_{\mbox{Zusammenfügen}}&amp;lt;/math&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
Wir bilden zuerst den Rekursionsbaum dieses Problems, wobei für jeden Knoten die Größe des Teilproblems angegeben ist, das dieser Knoeten lösen muss:&lt;br /&gt;
 &lt;br /&gt;
                                                 n    &lt;br /&gt;
                                               /    \&lt;br /&gt;
                                              /      \&lt;br /&gt;
                                             /        \&lt;br /&gt;
                                            /          \&lt;br /&gt;
                                           /            \&lt;br /&gt;
                                       (n/3)            (2n/3)&lt;br /&gt;
                                     /       \          /     \&lt;br /&gt;
                                    /         \        /       \&lt;br /&gt;
                                   /           \      /         \&lt;br /&gt;
                                (n/9)       (2n/9)  (2n/9)      (4n/9)&lt;br /&gt;
&lt;br /&gt;
Jeder Knoten ruft rekursiv seine Kindknoten auf und fügt dann deren Teilprobleme zusammen. Wir berechnen den Aufwand, den allein das Zusammenfügen auf jeder Ebene des Baumes verursacht. Auf der obersten Ebene (Ebene 1) gibt es nur ein Teilproblem, und der Aufwand ist c*n. Auf Ebene 2 haben wir zwei Teilprobleme mit Aufwand&lt;br /&gt;
:::&amp;lt;math&amp;gt;c\,\frac{n}{3}&amp;lt;/math&amp;gt; und &amp;lt;math&amp;gt;c\,\frac{2\,n}{3}&amp;lt;/math&amp;gt;&lt;br /&gt;
Der Gesamtaufwand für das Zusammenfügen auf Ebene 2 ist die Summe dieser Ausdrücke, wir erhalten also wieder c*n. Für Ebene 3 gilt wiederum&lt;br /&gt;
:::&amp;lt;math&amp;gt;c\left(\frac{n}{9}+\frac{2\,n}{9}+\frac{2\,n}{9}+\frac{4\,n}{9}\right)=c\,n&amp;lt;/math&amp;gt;&lt;br /&gt;
Offensichtlich gilt also für alle Ebenen, dass das Zusammenfügen der Teilprobleme insgesamt c*n Schritte erfordert. Zur Berechnung des Gesamtaufwands müssen wir nun noch die Anzahl der Ebenen, also die Tiefe d des Baumes, schätzen. Die Rekursion muss spätestens dann enden, wenn ein Teilproblem der Größe 1 erriecht wird, weil dies nicht weiter zerlegt werden kann. Die Rekursion endet also, wenn&lt;br /&gt;
:::&amp;lt;math&amp;gt;\left(\frac{2}{3}\right)^d n=1&amp;lt;/math&amp;gt;.&lt;br /&gt;
Auflösen dieser Formel nach d ergibt&lt;br /&gt;
:::&amp;lt;math&amp;gt;d=\log_\frac{3}{2}(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
Wir leiten daraus die '''Vermutung''' ab, dass für die Komplexität unseres Algorithmus gilt&lt;br /&gt;
:::&amp;lt;math&amp;gt;T(n) \in O\left(\log_\frac{3}{2}(n)\cdot c\cdot n\right)&amp;lt;/math&amp;gt;&lt;br /&gt;
Nach den Regeln der O-Notation vereinfacht sich dies aber zu&lt;br /&gt;
:::&amp;lt;math&amp;gt;T(n) \in O\left(n\,\log n\right)&amp;lt;/math&amp;gt;&lt;br /&gt;
Diese Vermutung muss aber noch formal bewiesen werden (der Rekursionsbaum reicht als Beweis nicht aus). Der Beweis erfolgt durch die '''Substitutionsmethode'''. Das bedeutet, dass wir unsere Vermutung auf der rechten Seite der Rekursionsformel einsetzen und beweisen, dass eine wahre Aussage herauskommt. Für hinreichend große n und hinreichend großes d kann die Vermutung geschrieben werden als&lt;br /&gt;
:::&amp;lt;math&amp;gt;T(n) \le d\,n\,\mbox{ld}(n) &amp;lt;/math&amp;gt;&lt;br /&gt;
(wir haben hier willkürlich Logarithmen zu Basis 2 eingesetzt -- die Basis in der O-Notation ist bekanntlich beliebig). Einsetzen in die Rekursionsformel liefert&lt;br /&gt;
:::&amp;lt;math&amp;gt;T(n) \le T\left(\frac{n}{3}\right)+ T\left(\frac{2n}{3}\right)+ c\,n \le d\,\frac{n}{3}\,\mbox{ld}\left(\frac{n}{3}\right)+ d\,\frac{2n}{3}\,\mbox{ld}\left(\frac{2n}{3}\right)+ c\,n&amp;lt;/math&amp;gt;&lt;br /&gt;
Durch Ausmultiplizieren der Klammern und Trennen der Logarithmen von Quotionten in Differenzen von Logarithmen erhalten wir&lt;br /&gt;
:::&amp;lt;math&amp;gt; T(n) \le d\frac{1}{3}\,n\,\mbox{ld}(n)-d\,\frac{1}{3}\,n\,\mbox{ld}(3)+d\,\frac{2}{3}\,n\,\mbox{ld}(n)+d\,\frac{2}{3}\,n\,\mbox{ld}(2)-d\,\frac{2}{3}\,n\,\mbox{ld}(3)+c\,n&amp;lt;/math&amp;gt;&lt;br /&gt;
Unter Beachtung von ld(2)=1 können wir die Terme wie folgt zusammenfassen&lt;br /&gt;
:::&amp;lt;math&amp;gt; T(n) \le d\,n\,\mbox{ld}(n)-d\,n\left(\mbox{ld}(3)-\frac{2}{3}\right)+c\,n&amp;lt;/math&amp;gt;&lt;br /&gt;
Falls unsere Vermutung richtig ist, muss die rechte Seite kleiner oder gleich &amp;lt;math&amp;gt;d\,n\,\mbox{ld}(n) &amp;lt;/math&amp;gt; sein. Um dies zeigen zu können, setzen wir&lt;br /&gt;
:::&amp;lt;math&amp;gt; d \ge \frac{c}{\mbox{ld}(3)-\frac{2}{3}}\, \Leftrightarrow\, d\left(\mbox{ld}(3)-\frac{2}{3}\right) \ge c&amp;lt;/math&amp;gt;&lt;br /&gt;
(nach den Regeln der O-Notation kann d beliebig gewählt werden, solange es hinreichend groß ist).&lt;br /&gt;
Wenn wir die Konstante c mit Hilfe dieses Ausdrucks ersetzen, erhalten wir&lt;br /&gt;
:::&amp;lt;math&amp;gt; T(n) \le d\,n\,\mbox{ld}(n)-d\,n\left(\mbox{ld}(3)-\frac{2}{3}\right)+c\,n \le &lt;br /&gt;
d\,n\,\mbox{ld}(n)-d\,n\left(\mbox{ld}(3)-\frac{2}{3}\right)+d\left(\mbox{ld}(3)-\frac{2}{3}\right)n&amp;lt;/math&amp;gt;&lt;br /&gt;
Die beiden letzten Terme heben sich aber gerade weg, und es bleibt übrig:&lt;br /&gt;
:::&amp;lt;math&amp;gt; T(n) \le d\,n\,\mbox{ld}(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
und somit&lt;br /&gt;
:::&amp;lt;math&amp;gt;T(n) \in O\left(n\,\log n\right)&amp;lt;/math&amp;gt;&lt;br /&gt;
w.z.b.w.&lt;br /&gt;
&lt;br /&gt;
Allgemein gilt, dass eine &amp;lt;u&amp;gt;ungleiche Aufteilung in Teilprobleme die Komplexität eines rekursiven Algorithmus nicht verschlechtert, falls das Teilungsverhältnis konstant bleibt&amp;lt;/u&amp;gt;, falls der nichtrekursive Aufwand f(n) auf jeder Ebene in O(n) ist. Das gilt aber nicht, wenn das Problem immer in ein Teilproblem konstanter Größe und den Rest geteilt wird. Dann gilt mit einer Konstante p&lt;br /&gt;
:::&amp;lt;math&amp;gt;T(n) = T(p) + T(n-p) + c\cdot n&amp;lt;/math&amp;gt;&lt;br /&gt;
und das Teilungsverhältnis wird umso schlechter, je größer n wird. Dies ist gerade der ungünstige Fall bei Quicksort, und wir haben gesehen, dass sich die Komplexität hier auf O(n&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;) verschlechtert.&lt;/div&gt;</summary>
		<author><name>Jschleic</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=File:Fibrek.jpg&amp;diff=2502</id>
		<title>File:Fibrek.jpg</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=File:Fibrek.jpg&amp;diff=2502"/>
				<updated>2008-07-21T11:06:55Z</updated>
		
		<summary type="html">&lt;p&gt;Jschleic: Baumrekursion zur Berechnung der Fibonacci-Zahlen&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Baumrekursion zur Berechnung der Fibonacci-Zahlen&lt;/div&gt;</summary>
		<author><name>Jschleic</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=File:Fibrek.png&amp;diff=2501</id>
		<title>File:Fibrek.png</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=File:Fibrek.png&amp;diff=2501"/>
				<updated>2008-07-21T11:04:58Z</updated>
		
		<summary type="html">&lt;p&gt;Jschleic: uploaded a new version of &amp;quot;Image:Fibrek.png&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Baum zur rekursiven Berechnung ein Fibonacci-Zahl&lt;/div&gt;</summary>
		<author><name>Jschleic</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=File:Fibrek.png&amp;diff=2500</id>
		<title>File:Fibrek.png</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=File:Fibrek.png&amp;diff=2500"/>
				<updated>2008-07-21T10:55:43Z</updated>
		
		<summary type="html">&lt;p&gt;Jschleic: Baum zur rekursiven Berechnung ein Fibonacci-Zahl&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Baum zur rekursiven Berechnung ein Fibonacci-Zahl&lt;/div&gt;</summary>
		<author><name>Jschleic</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Generizit%C3%A4t&amp;diff=1868</id>
		<title>Generizität</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Generizit%C3%A4t&amp;diff=1868"/>
				<updated>2008-06-26T12:46:05Z</updated>
		
		<summary type="html">&lt;p&gt;Jschleic: /* Iteratoren */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Ziel von generischer Programmierung  [http://de.wikipedia.org/wiki/Generische_Programmierung] ist es, Algorithmen und Datenstrukturen so zu entwerfen und zu implementieren, dass sie möglichst vielfältig verwendbar sind.&lt;br /&gt;
&lt;br /&gt;
'''Gemeint sind :'''&lt;br /&gt;
&lt;br /&gt;
*verschiedene Anwendungen                         &lt;br /&gt;
*mit vielen Kombinationsmöglichkeiten&lt;br /&gt;
*als wiederverwendbare Bibliothek&lt;br /&gt;
&lt;br /&gt;
--&amp;gt; ''' ohne Neuimplementation '''&lt;br /&gt;
*Code austauschen in Bibliotheken&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Motivation ===&lt;br /&gt;
An einem Beispiel wollen wir zeigen, wie ähnlich das Kopieren eines Containers für verschiedene Datentypen abläuft:&lt;br /&gt;
&lt;br /&gt;
====Code====&lt;br /&gt;
&lt;br /&gt;
 def copyArray(a):&lt;br /&gt;
   r =[]&lt;br /&gt;
   for k in a:&lt;br /&gt;
      r.append(k)&lt;br /&gt;
   return k&lt;br /&gt;
&lt;br /&gt;
 class Node :&lt;br /&gt;
  def__init__(self, data, next)&lt;br /&gt;
     self.data = data&lt;br /&gt;
     self.next = next&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 def copyArrayToList(a) :&lt;br /&gt;
    if len(a) == 0 : return None&lt;br /&gt;
       first = last = Node (a[0], None)&lt;br /&gt;
    for k in a[1:]  :&lt;br /&gt;
       last.next = Node(k, None)&lt;br /&gt;
       last = last.next&lt;br /&gt;
    return first&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 def copyListToArray(l):&lt;br /&gt;
     r = []&lt;br /&gt;
     while l is not in None :&lt;br /&gt;
         r.append(l.data)&lt;br /&gt;
         l = l.next&lt;br /&gt;
     return r&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Beobachtung  ====&lt;br /&gt;
&lt;br /&gt;
Für '''N Datenstrukuren''' ist der Implementationsaufwand &amp;lt;math&amp;gt;O(N^2) &amp;lt;/math&amp;gt;, wenn man je zwei Datenstrukturen ineinander umwandeln können will.&lt;br /&gt;
Alle Funktionen machen das gleiche mit einem uninteressantem Unterschied. Wir wollen daher im Folgenden eine Möglichkeit angeben, das kopieren der Daten zu vereinheitlichen.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
====Verbesserungsmöglichkeiten ====&lt;br /&gt;
'''Verbesserung durch Verallgemeinerung zweier Aspekte''' :&lt;br /&gt;
&lt;br /&gt;
*Navigieren durch die Quelldaten&lt;br /&gt;
*Aufbauen der Zieldatenstruktur&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Vereinheitlichung der Zieldatenstruktur :'''&lt;br /&gt;
*standardisierte Funktion &amp;quot;append&amp;quot;&lt;br /&gt;
*Array hat sie schon&lt;br /&gt;
*Liste : definiere Klasse DoublyLinkedList&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 class SentinelTag : pass     # keine Daten&lt;br /&gt;
 &lt;br /&gt;
 class DoublyLinkedNode:&lt;br /&gt;
  def__init__(self,data = sentinelTag(), next = None)&lt;br /&gt;
       self.data = data&lt;br /&gt;
       if next is None :&lt;br /&gt;
             self.prev = self.next = self&lt;br /&gt;
       else:&lt;br /&gt;
             self.next = next&lt;br /&gt;
             self.prev = next.prev&lt;br /&gt;
             next.prev.next = self&lt;br /&gt;
             next.prev = self&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
 def isSentinel(self ) : return isinstance( self.data, sentinelTag)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 class DoublyLinkedList :             # Realisiert doppelt verbundene kreisförmige Kette mit Sentinel                                                                                  &lt;br /&gt;
                                       #  als &amp;quot;Anker&amp;quot;&lt;br /&gt;
   def__init__(self):&lt;br /&gt;
            self.sentinel = DoublyLinkedNode()&lt;br /&gt;
            self.size = 0&lt;br /&gt;
       def__len__(self): return self.size   #len(l)&lt;br /&gt;
       def append(self, value):&lt;br /&gt;
           DoublyLinkedNode(value, self.sentinel)&lt;br /&gt;
           self.size += size&lt;br /&gt;
       def__iter__(self):&lt;br /&gt;
            return ListIterator(self.sentinel.next)&lt;br /&gt;
       def reverseIterator(self):&lt;br /&gt;
             return ListIterator(self.sentinel.prev)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
====verbesserter Code ====&lt;br /&gt;
Mit diesen Schnittstellen reicht uns nun eine einzige Methode zum Kopieren eines Containers aus. &lt;br /&gt;
&lt;br /&gt;
 def genericCopy (quelle, ziele) :&lt;br /&gt;
    for k in quelle :&lt;br /&gt;
       ziel.append(k)&lt;br /&gt;
    return ziel&lt;br /&gt;
 &lt;br /&gt;
 liste = genericCopy(array, DoublyLinkedList())       # Statt copyArrayToList&lt;br /&gt;
 array2 = genericCopy(array,[])                       # Statt copyArray &lt;br /&gt;
 array3 = genericCopy(liste,[])                       # Statt copyListToArray&lt;br /&gt;
&lt;br /&gt;
=== Iteratoren ===&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
'''Definition Iterator:''' siehe [http://de.wikipedia.org/wiki/Iterator]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Navigation in der Quelldatenstruktur ( &amp;lt;u&amp;gt;Iteratoren&amp;lt;/u&amp;gt; ) soll&amp;lt;br/&amp;gt; für alle Datenstrukturen funktionieren&lt;br /&gt;
&lt;br /&gt;
Ein Iterator ist ein Objekt,&lt;br /&gt;
*das auf ein Element des Containers zeigt&lt;br /&gt;
*das zum nächsten Element weiter rücken kann&lt;br /&gt;
*das anzeigt, wenn das Ende der Sequenz erreicht ist &lt;br /&gt;
&lt;br /&gt;
'''Beispiel:'''&lt;br /&gt;
 class ListIterator:&lt;br /&gt;
       def __init__(self, node):&lt;br /&gt;
            self.node = node&lt;br /&gt;
       def next(self):&lt;br /&gt;
             if self.node.isSentinel():&lt;br /&gt;
                   raise StopIteration()            #Python Konvention&lt;br /&gt;
             v = self.node.data&lt;br /&gt;
             self.node = self.node.next            # zeigt Ende der Sequenz&lt;br /&gt;
             return v                                   # Pythonkonvention, gebe vorigen Wert zurück&lt;br /&gt;
 &lt;br /&gt;
       def __iter__(self):&lt;br /&gt;
          return ListIterator(self.node)            # Pythonkonvention, Kopie des Iterators zurückgeben&lt;br /&gt;
             #oder stattdessen besser:&lt;br /&gt;
          return self.__class__(self.node)          # ist allgemeiner&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Was tut Python bei &amp;quot; for k in quelle&amp;quot;( in genericCopy ) ?''': &amp;lt;br /&amp;gt;&lt;br /&gt;
Will man in Python alle Elemente eines Containers durchlaufen, so tut man dies leicht mit einem Statement der Form &amp;lt;code&amp;gt;for k in quelle&amp;lt;/code&amp;gt;. Was passiert dabei eigentlich? &amp;lt;br /&amp;gt;&lt;br /&gt;
Das Schlüsselwort &amp;lt;code&amp;gt;for&amp;lt;/code&amp;gt; ruft dabei die Methode &amp;lt;code&amp;gt;iter()&amp;lt;/code&amp;gt; der entsprechenden Klasse auf, die einen Zeiger auf ein Iterator-Objekt zurückgibt. Dieses Objekt definiert eine Methode &amp;lt;code&amp;gt;next()&amp;lt;/code&amp;gt;, womit man das nächste Element der Datenstruktur bekommen kann. Wenn keine weiteren Elemente mehr vorhanden sind, wird eine Exception &amp;lt;code&amp;gt;StopIteration&amp;lt;/code&amp;gt; ausgelöst.&lt;br /&gt;
 iter = quelle.__iter__()&lt;br /&gt;
 try :&lt;br /&gt;
        while True :&lt;br /&gt;
              k = iter.next()&lt;br /&gt;
              ...             # Schleifeninhalt&lt;br /&gt;
 except StopIteration: pass&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Rückwärts kopieren :'''&lt;br /&gt;
Um eine Liste rückwärts zu kopieren, könnten wir also folgenden Iterator verwenden:&lt;br /&gt;
 class ReverseListIterator(ListIterator)&lt;br /&gt;
   def next(self):&lt;br /&gt;
       if self.node.isSentinel(): raise StopIteration()&lt;br /&gt;
       v = self.node.data&lt;br /&gt;
       self.node = self.node.prev&lt;br /&gt;
       return v&lt;br /&gt;
 &lt;br /&gt;
 revArray = genericCopy(list.reverseIterator(), []),          # Liste in ein Array kopieren&lt;br /&gt;
 revList = genericCopy(reversed(array), DoublyLinkedList())   # Array umdrehen und dann in eine Liste kopieren&lt;br /&gt;
&lt;br /&gt;
===Funktoren===&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
'''Definition eines Funktors :''' siehe [http://de.wikipedia.org/wiki/Funktor]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Verallgemeinerung auf Funktionen die &amp;quot; etwas tun&amp;quot;:'''&lt;br /&gt;
&lt;br /&gt;
     def sumArray(a):&lt;br /&gt;
         s = 0&lt;br /&gt;
         for k in a :&lt;br /&gt;
             s += a       # s = add(s,k)&lt;br /&gt;
       return a&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 def maxList(l):&lt;br /&gt;
    m = -1111111111111111&lt;br /&gt;
    while not l.isSentinel:&lt;br /&gt;
         m = max(m, l.data)                    # max ist eingebaute Funktion in Python&lt;br /&gt;
         l =l.next&lt;br /&gt;
    return m&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
''' Verallgemeinerung durch Funktoren :'''&lt;br /&gt;
&lt;br /&gt;
*Funktor muss &amp;quot;callable&amp;quot; sein : falls f Funktor ist, funktioniert v = f(a1, a2,...) &lt;br /&gt;
*Funktion, oder Objekt bei dem die Funktion __call___ definiert ist.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 def doSomethingGeneric(functor,iterator, initial):&lt;br /&gt;
       for k in iterator&lt;br /&gt;
             initial = functor(initial, k)&lt;br /&gt;
    return initial&lt;br /&gt;
&lt;br /&gt;
'''Statt maxList:'''&lt;br /&gt;
&lt;br /&gt;
 m = doSomethingGeneric(max,list, -1111111111111111)  &lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
'''Statt sumArray :'''&lt;br /&gt;
&lt;br /&gt;
 def add(x,y): return x + y&lt;br /&gt;
   s = doSomethingGeneric(add, array, 0)&lt;br /&gt;
&lt;br /&gt;
'''Statt genericCopy :'''&lt;br /&gt;
&lt;br /&gt;
 def append(x,y):&lt;br /&gt;
      x.append(y)&lt;br /&gt;
    return x&lt;br /&gt;
 array4 = doSomeThingGeneric(append, array, [])&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
'''doSomethingGeneric''' gibt es in vielen Programmiersprachen :&lt;br /&gt;
&lt;br /&gt;
*in Python : reduce     &lt;br /&gt;
*in C++ : accumulate&lt;br /&gt;
...funktionale Sprachen (Lisp, Haskell...)&lt;br /&gt;
&lt;br /&gt;
'''verwandte generische Funktionen'''&lt;br /&gt;
&lt;br /&gt;
map: &lt;br /&gt;
&lt;br /&gt;
[x1, x2,...] --&amp;gt; [f(x1),f(x2),...]          # Funktor mit einem Argument&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Offered Interface versus Required Interface===&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Interface:'''&lt;br /&gt;
*standardisierte Schnittstelle zwischen Algorithmen und Datenstruktur&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
====Offered Interface:====&lt;br /&gt;
&lt;br /&gt;
*Funktionalität, die eine Datenstruktur anbietet.&lt;br /&gt;
*Die Datensruktur sollte möglichst vielseitig sein.&lt;br /&gt;
&lt;br /&gt;
'''z.B. PythonList unterstützt Funktionalität von :''' &lt;br /&gt;
&lt;br /&gt;
* Dynamisches Array&lt;br /&gt;
* Stack&lt;br /&gt;
* Deque&lt;br /&gt;
* LinkedList&lt;br /&gt;
&lt;br /&gt;
*standardisiert durch abstrakte Datentypen&lt;br /&gt;
&lt;br /&gt;
====Required Interface:====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
*Funktionalität, die von einem Algorithmus benutzt wird&lt;br /&gt;
*das '''required Interface''' ist meist weniger als '''das offered Interface'''&lt;br /&gt;
&lt;br /&gt;
z.B.:&lt;br /&gt;
&lt;br /&gt;
'''RI''': lesender Zugriff &amp;lt;br/&amp;gt;&lt;br /&gt;
'''OI''' schreibender Zugriff  Konstruktor, remove...&lt;br /&gt;
* standardisiert durch Konzepte&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* ADT sind Sammlungen zusammengehörender Konzepte&lt;br /&gt;
* '''RIs''' sollten &amp;lt;u&amp;gt;minimal&amp;lt;/u&amp;gt; sein&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
====Konzepte ( + Hierarchie)====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* copy Constructible ( Python:Klassen, die man auf deepcopy anwenden kann, copy.deepcopy)&lt;br /&gt;
(Gegenteil : Singleton)&lt;br /&gt;
* Default Constructible (v1 = v.__class__() ist aufrufbar ) # DoublylinkedNode&lt;br /&gt;
* EqualityComparable('=='), LessThanComparable('&amp;lt;')&lt;br /&gt;
* ThreeWayComparable(__cmp__ ist aufrufbar)&lt;br /&gt;
* Indexable(&amp;quot;a[k]&amp;quot;, k ist Integer)&lt;br /&gt;
* Mapping(&amp;quot;a[key]&amp;quot;, key ist arbitrary)&lt;br /&gt;
* Hashable(__hash__ für key)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Iteratoren :(C++ : ForwardIterator : next, BidirektionalIterator : next, prev ,&amp;lt;br/&amp;gt; RandomAccessIterator : next[k])&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 Container :               Sequence                                       Array&lt;br /&gt;
&lt;br /&gt;
====Mathematische Konzepte :====&lt;br /&gt;
&lt;br /&gt;
 Addable(__add__)&lt;br /&gt;
 Subtractable(__sub__)&lt;br /&gt;
 Multiplyable(__mul__)&lt;br /&gt;
 Dividable(__div__)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Ein offered Interface ist mehr als ein required Interface.'''&lt;/div&gt;</summary>
		<author><name>Jschleic</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Generizit%C3%A4t&amp;diff=1866</id>
		<title>Generizität</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Generizit%C3%A4t&amp;diff=1866"/>
				<updated>2008-06-26T12:29:55Z</updated>
		
		<summary type="html">&lt;p&gt;Jschleic: /* Beispiel =&amp;gt; Motivation */  Verbesserung der Gliederung&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Ziel von generischer Programmierung  [http://de.wikipedia.org/wiki/Generische_Programmierung] ist es, Algorithmen und Datenstrukturen so zu entwerfen und zu implementieren, dass sie möglichst vielfältig verwendbar sind.&lt;br /&gt;
&lt;br /&gt;
'''Gemeint sind :'''&lt;br /&gt;
&lt;br /&gt;
*verschiedene Anwendungen                         &lt;br /&gt;
*mit vielen Kombinationsmöglichkeiten&lt;br /&gt;
*als wiederverwendbare Bibliothek&lt;br /&gt;
&lt;br /&gt;
--&amp;gt; ''' ohne Neuimplementation '''&lt;br /&gt;
*Code austauschen in Bibliotheken&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Motivation ===&lt;br /&gt;
An einem Beispiel wollen wir zeigen, wie ähnlich das Kopieren eines Containers für verschiedene Datentypen abläuft:&lt;br /&gt;
&lt;br /&gt;
====Code====&lt;br /&gt;
&lt;br /&gt;
 def copyArray(a):&lt;br /&gt;
   r =[]&lt;br /&gt;
   for k in a:&lt;br /&gt;
      r.append(k)&lt;br /&gt;
   return k&lt;br /&gt;
&lt;br /&gt;
 class Node :&lt;br /&gt;
  def__init__(self, data, next)&lt;br /&gt;
     self.data = data&lt;br /&gt;
     self.next = next&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 def copyArrayToList(a) :&lt;br /&gt;
    if len(a) == 0 : return None&lt;br /&gt;
       first = last = Node (a[0], None)&lt;br /&gt;
    for k in a[1:]  :&lt;br /&gt;
       last.next = Node(k, None)&lt;br /&gt;
       last = last.next&lt;br /&gt;
    return first&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 def copyListToArray(l):&lt;br /&gt;
     r = []&lt;br /&gt;
     while l is not in None :&lt;br /&gt;
         r.append(l.data)&lt;br /&gt;
         l = l.next&lt;br /&gt;
     return r&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Beobachtung  ====&lt;br /&gt;
&lt;br /&gt;
Für '''N Datenstrukuren''' ist der Implementationsaufwand &amp;lt;math&amp;gt;O(N^2) &amp;lt;/math&amp;gt;, wenn man je zwei Datenstrukturen ineinander umwandeln können will.&lt;br /&gt;
Alle Funktionen machen das gleiche mit einem uninteressantem Unterschied. Wir wollen daher im Folgenden eine Möglichkeit angeben, das kopieren der Daten zu vereinheitlichen.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
====Verbesserungsmöglichkeiten ====&lt;br /&gt;
'''Verbesserung durch Verallgemeinerung zweier Aspekte''' :&lt;br /&gt;
&lt;br /&gt;
*Navigieren durch die Quelldaten&lt;br /&gt;
*Aufbauen der Zieldatenstruktur&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Vereinheitlichung der Zieldatenstruktur :'''&lt;br /&gt;
*standardisierte Funktion &amp;quot;append&amp;quot;&lt;br /&gt;
*Array hat sie schon&lt;br /&gt;
*Liste : definiere Klasse DoublyLinkedList&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 class SentinelTag : pass     # keine Daten&lt;br /&gt;
 &lt;br /&gt;
 class DoublyLinkedNode:&lt;br /&gt;
  def__init__(self,data = sentinelTag(), next = None)&lt;br /&gt;
       self.data = data&lt;br /&gt;
       if next is None :&lt;br /&gt;
             self.prev = self.next = self&lt;br /&gt;
       else:&lt;br /&gt;
             self.next = next&lt;br /&gt;
             self.prev = next.prev&lt;br /&gt;
             next.prev.next = self&lt;br /&gt;
             next.prev = self&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
 def isSentinel(self ) : return isinstance( self.data, sentinelTag)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 class DoublyLinkedList :             # Realisiert doppelt verbundene kreisförmige Kette mit Sentinel                                                                                  &lt;br /&gt;
                                       #  als &amp;quot;Anker&amp;quot;&lt;br /&gt;
   def__init__(self):&lt;br /&gt;
            self.sentinel = DoublyLinkedNode()&lt;br /&gt;
            self.size = 0&lt;br /&gt;
       def__len__(self): return self.size   #len(l)&lt;br /&gt;
       def append(self, value):&lt;br /&gt;
           DoublyLinkedNode(value, self.sentinel)&lt;br /&gt;
           self.size += size&lt;br /&gt;
       def__iter__(self):&lt;br /&gt;
            return ListIterator(self.sentinel.next)&lt;br /&gt;
       def reverseIterator(self):&lt;br /&gt;
             return ListIterator(self.sentinel.prev)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
====verbesserter Code ====&lt;br /&gt;
Mit diesen Schnittstellen reicht uns nun eine einzige Methode zum Kopieren eines Containers aus. &lt;br /&gt;
&lt;br /&gt;
 def genericCopy (quelle, ziele) :&lt;br /&gt;
    for k in quelle :&lt;br /&gt;
       ziel.append(k)&lt;br /&gt;
    return ziel&lt;br /&gt;
 &lt;br /&gt;
 liste = genericCopy(array, DoublyLinkedList())       # Statt copyArrayToList&lt;br /&gt;
 array2 = genericCopy(array,[])                       # Statt copyArray &lt;br /&gt;
 array3 = genericCopy(liste,[])                       # Statt copyListToArray&lt;br /&gt;
&lt;br /&gt;
=== Iteratoren ===&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
'''Definition Iterator:''' siehe [http://de.wikipedia.org/wiki/Iterator]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Navigation in der Quelldatenstruktur ( &amp;lt;u&amp;gt;Iteratoren&amp;lt;/u&amp;gt; ) soll&amp;lt;br/&amp;gt; für alle Datenstrukturen funktionieren&lt;br /&gt;
&lt;br /&gt;
*Objekt, das auf ein Element des Containers zeigt&lt;br /&gt;
*Zum nächsten Element weiter rücken kann&lt;br /&gt;
*Zeigt an, wenn das Ende der Sequenz erreicht ist &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 class ListIterator:&lt;br /&gt;
       def__init__(self, node):&lt;br /&gt;
            self.node = node&lt;br /&gt;
       def next(self):&lt;br /&gt;
             if self.node.isSentinel():&lt;br /&gt;
                   raise StopIteration()            #Python Konvention&lt;br /&gt;
             v = self.node.data&lt;br /&gt;
             self.node = self.node.next            # zeigt Ende der Sequenz&lt;br /&gt;
        return v                                   # Pythonkonvention, gebe vorigen Wert zurück&lt;br /&gt;
&lt;br /&gt;
    def__iter__(self):&lt;br /&gt;
       return ListIterator(self.node)            # Pythonkonvention, Kopie des Iterators zurückgeben&lt;br /&gt;
&lt;br /&gt;
'''besser stattdessen''' :&lt;br /&gt;
&lt;br /&gt;
      return self.__class__(self.node)          # ist allgemeiner&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Was tut Python bei''' &amp;quot; for k in quelle&amp;quot;( in genericCopy ) ?:&lt;br /&gt;
 &lt;br /&gt;
 iter = quelle.__iter__()&lt;br /&gt;
 try :&lt;br /&gt;
        while True :&lt;br /&gt;
              k = iter.next()&lt;br /&gt;
              ...             # Schleifeninhalt&lt;br /&gt;
 except StopIteration: pass&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Rückwärts kopieren :'''&lt;br /&gt;
&lt;br /&gt;
 class ReverseListIterator(ListIterator)&lt;br /&gt;
   def next(self):&lt;br /&gt;
         if self.node.isSentinel(): raise StopIteration()&lt;br /&gt;
       v = self.node.data&lt;br /&gt;
       self.node = self.node.prev&lt;br /&gt;
       return v&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 revArray = genericCopy(list.reverseIterator(), []),&lt;br /&gt;
 revList = genericCopy(reversed(array), DoublyLinkedList())&lt;br /&gt;
&lt;br /&gt;
===Funktoren===&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
'''Definition eines Funktors :''' siehe [http://de.wikipedia.org/wiki/Funktor]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Verallgemeinerung auf Funktionen die &amp;quot; etwas tun&amp;quot;:'''&lt;br /&gt;
&lt;br /&gt;
     def sumArray(a):&lt;br /&gt;
         s = 0&lt;br /&gt;
         for k in a :&lt;br /&gt;
             s += a       # s = add(s,k)&lt;br /&gt;
       return a&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 def maxList(l):&lt;br /&gt;
    m = -1111111111111111&lt;br /&gt;
    while not l.isSentinel:&lt;br /&gt;
         m = max(m, l.data)                    # max ist eingebaute Funktion in Python&lt;br /&gt;
         l =l.next&lt;br /&gt;
    return m&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
''' Verallgemeinerung durch Funktoren :'''&lt;br /&gt;
&lt;br /&gt;
*Funktor muss &amp;quot;callable&amp;quot; sein : falls f Funktor ist, funktioniert v = f(a1, a2,...) &lt;br /&gt;
*Funktion, oder Objekt bei dem die Funktion __call___ definiert ist.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 def doSomethingGeneric(functor,iterator, initial):&lt;br /&gt;
       for k in iterator&lt;br /&gt;
             initial = functor(initial, k)&lt;br /&gt;
    return initial&lt;br /&gt;
&lt;br /&gt;
'''Statt maxList:'''&lt;br /&gt;
&lt;br /&gt;
 m = doSomethingGeneric(max,list, -1111111111111111)  &lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
'''Statt sumArray :'''&lt;br /&gt;
&lt;br /&gt;
 def add(x,y): return x + y&lt;br /&gt;
   s = doSomethingGeneric(add, array, 0)&lt;br /&gt;
&lt;br /&gt;
'''Statt genericCopy :'''&lt;br /&gt;
&lt;br /&gt;
 def append(x,y):&lt;br /&gt;
      x.append(y)&lt;br /&gt;
    return x&lt;br /&gt;
 array4 = doSomeThingGeneric(append, array, [])&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
'''doSomethingGeneric''' gibt es in vielen Programmiersprachen :&lt;br /&gt;
&lt;br /&gt;
*in Python : reduce     &lt;br /&gt;
*in C++ : accumulate&lt;br /&gt;
...funktionale Sprachen (Lisp, Haskell...)&lt;br /&gt;
&lt;br /&gt;
'''verwandte generische Funktionen'''&lt;br /&gt;
&lt;br /&gt;
map: &lt;br /&gt;
&lt;br /&gt;
[x1, x2,...] --&amp;gt; [f(x1),f(x2),...]          # Funktor mit einem Argument&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Offered Interface versus Required Interface===&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Interface:'''&lt;br /&gt;
*standardisierte Schnittstelle zwischen Algorithmen und Datenstruktur&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
====Offered Interface:====&lt;br /&gt;
&lt;br /&gt;
*Funktionalität, die eine Datenstruktur anbietet.&lt;br /&gt;
*Die Datensruktur sollte möglichst vielseitig sein.&lt;br /&gt;
&lt;br /&gt;
'''z.B. PythonList unterstützt Funktionalität von :''' &lt;br /&gt;
&lt;br /&gt;
* Dynamisches Array&lt;br /&gt;
* Stack&lt;br /&gt;
* Deque&lt;br /&gt;
* LinkedList&lt;br /&gt;
&lt;br /&gt;
*standardisiert durch abstrakte Datentypen&lt;br /&gt;
&lt;br /&gt;
====Required Interface:====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
*Funktionalität, die von einem Algorithmus benutzt wird&lt;br /&gt;
*das '''required Interface''' ist meist weniger als '''das offered Interface'''&lt;br /&gt;
&lt;br /&gt;
z.B.:&lt;br /&gt;
&lt;br /&gt;
'''RI''': lesender Zugriff &amp;lt;br/&amp;gt;&lt;br /&gt;
'''OI''' schreibender Zugriff  Konstruktor, remove...&lt;br /&gt;
* standardisiert durch Konzepte&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* ADT sind Sammlungen zusammengehörender Konzepte&lt;br /&gt;
* '''RIs''' sollten &amp;lt;u&amp;gt;minimal&amp;lt;/u&amp;gt; sein&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
====Konzepte ( + Hierarchie)====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* copy Constructible ( Python:Klassen, die man auf deepcopy anwenden kann, copy.deepcopy)&lt;br /&gt;
(Gegenteil : Singleton)&lt;br /&gt;
* Default Constructible (v1 = v.__class__() ist aufrufbar ) # DoublylinkedNode&lt;br /&gt;
* EqualityComparable('=='), LessThanComparable('&amp;lt;')&lt;br /&gt;
* ThreeWayComparable(__cmp__ ist aufrufbar)&lt;br /&gt;
* Indexable(&amp;quot;a[k]&amp;quot;, k ist Integer)&lt;br /&gt;
* Mapping(&amp;quot;a[key]&amp;quot;, key ist arbitrary)&lt;br /&gt;
* Hashable(__hash__ für key)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Iteratoren :(C++ : ForwardIterator : next, BidirektionalIterator : next, prev ,&amp;lt;br/&amp;gt; RandomAccessIterator : next[k])&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 Container :               Sequence                                       Array&lt;br /&gt;
&lt;br /&gt;
====Mathematische Konzepte :====&lt;br /&gt;
&lt;br /&gt;
 Addable(__add__)&lt;br /&gt;
 Subtractable(__sub__)&lt;br /&gt;
 Multiplyable(__mul__)&lt;br /&gt;
 Dividable(__div__)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Ein offered Interface ist mehr als ein required Interface.'''&lt;/div&gt;</summary>
		<author><name>Jschleic</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Graphen_und_Graphenalgorithmen&amp;diff=1863</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=1863"/>
				<updated>2008-06-25T22:44:26Z</updated>
		
		<summary type="html">&lt;p&gt;Jschleic: /* Minimaler Spannbaum (tree-MST) */ formeln verbessert&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Einführung zu Graphen ==&lt;br /&gt;
&lt;br /&gt;
=== Motivation ===&lt;br /&gt;
&lt;br /&gt;
==== Königsberger - Brückenproblem ====&lt;br /&gt;
(1736 Euler)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Image:Koenigsberg.jpg]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Königsberger Brücken:&lt;br /&gt;
&lt;br /&gt;
Spaziergang durch Königsberg, so dass alle Brücken nur einmal überquert werden.&lt;br /&gt;
&lt;br /&gt;
Geometrie:&lt;br /&gt;
Topologie&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;
&lt;br /&gt;
* '''Definition: ungerichteter Graph'''&lt;br /&gt;
&lt;br /&gt;
Ein ungerichteter Graph G = ( V, E )&lt;br /&gt;
&lt;br /&gt;
** V ist endliche Menge von Knoten (vertices)&lt;br /&gt;
** E c V × V (edges)&lt;br /&gt;
&lt;br /&gt;
Ein Graph heißt ungerichtet, wenn zusätzlich gilt:&lt;br /&gt;
&lt;br /&gt;
(x,y) ∈ E =&amp;gt; (y,x) ∈ E (symmetrie)&lt;br /&gt;
&lt;br /&gt;
Bsp:&lt;br /&gt;
&lt;br /&gt;
gerichteter Graph&lt;br /&gt;
[[Image:digraph.png|gerichteter Graph]]&lt;br /&gt;
&lt;br /&gt;
 ungerichtet&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;
&lt;br /&gt;
   &lt;br /&gt;
&lt;br /&gt;
Bsp:&lt;br /&gt;
&lt;br /&gt;
* Landkarten:&lt;br /&gt;
** Knoten: Länder&lt;br /&gt;
** Kanten: gem. Grenzen&lt;br /&gt;
&lt;br /&gt;
* Schaltkreis:&lt;br /&gt;
** Knoten: Gatter&lt;br /&gt;
** Kanten: Verbindungen&lt;br /&gt;
&lt;br /&gt;
* Chemie (Summenformeln):&lt;br /&gt;
** Knoten: Elemente&lt;br /&gt;
** Kanten: Bindungen &lt;br /&gt;
&lt;br /&gt;
* Soziologie (StudieVZ)&lt;br /&gt;
** Soziogramm&lt;br /&gt;
*** Knoten: Personen&lt;br /&gt;
*** Kanten: Freund von ...&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* '''Definition: Vollständige Graphen'''&lt;br /&gt;
&lt;br /&gt;
Bei vollständigen Graphen ist jeder Knoten mit allen anderen Knoten verbunden.&lt;br /&gt;
&lt;br /&gt;
E =  U V (v,w) u (w,v)   |  v ∈ V, w ∈ V, u != w&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''&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?&lt;br /&gt;
&lt;br /&gt;
== Repräsentation von Graphen ==&lt;br /&gt;
&lt;br /&gt;
Sei G = ( V, E ) geg und liege V in einer lineraren Sortierung vor.&lt;br /&gt;
V = { v1, ...., vn }&lt;br /&gt;
&lt;br /&gt;
== Adjazenzmatrix ==&lt;br /&gt;
&lt;br /&gt;
AG = aij = {1 falls (vi, vj) ∈ E ; sonst 0}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Bsp:&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;
 AG = (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;
&lt;br /&gt;
== Adjezenzlisten ==&lt;br /&gt;
&lt;br /&gt;
al(v) = {v' ∈ V | (u,u') ∈ E}&lt;br /&gt;
Lg = ((v1, al(v1)), ...., (vn, al(vn))&lt;br /&gt;
&lt;br /&gt;
Python:&lt;br /&gt;
&lt;br /&gt;
 Array von Arrays [[...],[...],...,[...]]&lt;br /&gt;
                     0     1         n&lt;br /&gt;
&lt;br /&gt;
* '''Definition: Teilgraphen'''&lt;br /&gt;
&lt;br /&gt;
Ein Graph G' = (v',E') ist ein Teilgraph, wenn gilt:&lt;br /&gt;
&lt;br /&gt;
** v' c V &lt;br /&gt;
** E' c E &lt;br /&gt;
&lt;br /&gt;
Er heißt erzegender Graph, wenn zusätzlich gilt:&lt;br /&gt;
&lt;br /&gt;
** v' = V&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* '''Definition: Knotengrade'''&lt;br /&gt;
Für G = (v,E)und v ∈ V&lt;br /&gt;
grad(v) = |{v' ∈ V | v,v'∈ E}|&lt;br /&gt;
out_grad(v) = |   -&amp;quot;&amp;quot;-       |&lt;br /&gt;
in_grad(v)  = |{v'∈ V| (v',v) ∈ E}|&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Bsp: &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 ungerichtet&lt;br /&gt;
 &lt;br /&gt;
  c&lt;br /&gt;
 || \&lt;br /&gt;
 ||  \&lt;br /&gt;
  b   d          grad(a) = | {b,b,d} | = 3&lt;br /&gt;
 ||  /&lt;br /&gt;
 || /&lt;br /&gt;
  a&lt;br /&gt;
 &lt;br /&gt;
  &lt;br /&gt;
 gerichtet&lt;br /&gt;
 &lt;br /&gt;
  c←&lt;br /&gt;
  | \&lt;br /&gt;
  ↓  \&lt;br /&gt;
  b←--d         out_grad(d) = 2 = | {c,b} |&lt;br /&gt;
  |  /→          in_grad(d) = 1 = | {a} |&lt;br /&gt;
  ↓ /&lt;br /&gt;
  a&lt;br /&gt;
&lt;br /&gt;
* '''Definition: Wege'''&lt;br /&gt;
&lt;br /&gt;
Sei G = (v,E)&lt;br /&gt;
&lt;br /&gt;
** Für v0 ∈ V ist (v0) ein Weg in G&lt;br /&gt;
** Für Knoten v1,...vn,vn+1 und eine Kante (vn,vn+1) ∈ E ist mit einem Weg (v0,....vn) in G auch (v0,...,vn,vn+1) ein Weg in G.&lt;br /&gt;
&lt;br /&gt;
Also: Nichtleere Folgen von Knoten die durch eine Kante verbunden sind.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 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 == &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;
== Kreis == &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;
&lt;br /&gt;
== Zyklen ==&lt;br /&gt;
&lt;br /&gt;
    O&lt;br /&gt;
   /  \&lt;br /&gt;
  O    O&lt;br /&gt;
    \  |&lt;br /&gt;
     \ |   Wie Kreis nur ohne (vi != vj)&lt;br /&gt;
  O====O&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* '''Definition: planare Graphen'''&lt;br /&gt;
&lt;br /&gt;
Ist ein Graph, der auf einer Ebene gezeichnet werden ''kann'', sodass sich die Kanten nicht schneiden!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Bsp:&lt;br /&gt;
&lt;br /&gt;
 1)  &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;
 2)&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;
 3)&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 ''Regionen'' auch ausserhalb der Figur ist eine Region&lt;br /&gt;
 |@      |&lt;br /&gt;
 |-------|&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
1),2) und 3) sind planare Graphen.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Der K5 Graph ist kein planarer Graph da sich zwangsweise Kanten schneiden.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* '''Definition: dualer Graph'''&lt;br /&gt;
&lt;br /&gt;
Der duale Graph eines geg. planaren Graphs G' ist ein Graph mit&lt;br /&gt;
&lt;br /&gt;
** Knoten für jede Region&lt;br /&gt;
** Für jede Kante aus E gilt es gibt eine Kante, die die angrenzende Region mit Knoten verbindet.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 dualer Graph&lt;br /&gt;
&lt;br /&gt;
      O------O&lt;br /&gt;
      |     /| \&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;
&lt;br /&gt;
&lt;br /&gt;
* '''Definition: erreichbar'''&lt;br /&gt;
&lt;br /&gt;
 W ∈ V ist erreichbar von v ∈ G gdw.:&lt;br /&gt;
 es Existiert Weg(v,...w)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* '''Definition: Zusammenhang'''&lt;br /&gt;
&lt;br /&gt;
 G heißt zusammenhängend, wenn für Alle v,w ∈V gilt:&lt;br /&gt;
 w ist erreichbar von V&lt;br /&gt;
&lt;br /&gt;
== Bäume ==&lt;br /&gt;
&lt;br /&gt;
* '''Definition: Baum'''&lt;br /&gt;
&lt;br /&gt;
 Ein Baum ist ein zusammenhängender, kreisfreier Graph.&lt;br /&gt;
&lt;br /&gt;
Bsp.: Binary Search Tree&lt;br /&gt;
&lt;br /&gt;
* '''Definition: erzeugender Baum'''&lt;br /&gt;
&lt;br /&gt;
 für G = (v,E) ist ein erzeigender Teilgraph mit Baumeigenschaft&lt;br /&gt;
&lt;br /&gt;
Bsp.: &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    O    O&lt;br /&gt;
   /    /   &lt;br /&gt;
  O    O    O&lt;br /&gt;
  |  /    /   &lt;br /&gt;
  | /    /    &lt;br /&gt;
  O----O----O&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.get()&lt;br /&gt;
     node = q.get()&lt;br /&gt;
     for t in q[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;
&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;
&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;
        '''&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;
        '''&lt;br /&gt;
        def visit(graph, node, visited):&lt;br /&gt;
                '''&lt;br /&gt;
                visited ist Array mit Flags fuer besuchte Knoten&lt;br /&gt;
                '''&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;
&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 moeglicher 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;
* fuer ungerichtete Graphen gilt zusaetzlich: 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;
                '''&lt;br /&gt;
                anchor ist Anker der aktuellen ZK&lt;br /&gt;
                '''&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;
                '''&lt;br /&gt;
                Prueft auf anchors[k]==k&lt;br /&gt;
                '''&lt;br /&gt;
                while anchors[k] != k:&lt;br /&gt;
                        k = anchor[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;
                anchor[node] = findAnchor(anchors, node)&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;
&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;
&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;
&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;
&lt;br /&gt;
&lt;br /&gt;
===Minimaler Spannbaum (tree-MST)===&lt;br /&gt;
gegeben: gewichteter, zusammenhängender Graph G &amp;lt;br /&amp;gt;&lt;br /&gt;
gesucht: 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 \sum verringern (eine Kante weglassen)&lt;br /&gt;
Anwendungen: Wie verbindet man n Punkte mit möglichst wenigen kurzen Straßen&lt;br /&gt;
&lt;br /&gt;
====Algorithmus von Prim====&lt;br /&gt;
&lt;br /&gt;
 def prim(graph):&lt;br /&gt;
 	heap = []&lt;br /&gt;
 	visited = [False]*len(graph)&lt;br /&gt;
 	sum = 0&lt;br /&gt;
 	r = []&lt;br /&gt;
 	for neighbor in graph[0]:&lt;br /&gt;
 		heapq.heappush(heap, (neighbor[1], 0, neighbor[0]))&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&lt;br /&gt;
 		sum += wn&lt;br /&gt;
 		r.append([start, ziel])&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 Krushal====&lt;br /&gt;
Idee: wie beim Union-Find-Algorithmus für Zusammenhangskomponenten&lt;br /&gt;
# Behandle jeden Knoten als Baum für sich&lt;br /&gt;
# Fasse zwei Bäume zu neuem Baum zusammen&lt;br /&gt;
für MST: betrachte dazu die Kanten in aufsteigender Reihenfolge der Gewichte&lt;br /&gt;
(ignoriere Kanten zw. Knoten in gleichem Baum)&lt;br /&gt;
=&amp;gt; geeignet für Übungsaufgabe&lt;br /&gt;
&lt;br /&gt;
=== Problem des Handlungsreisenden (travelling salesman problem - TSP)===&lt;br /&gt;
gegeben: zusammenhängender, gewichteter Graph (oft vollständiger Graph)&lt;br /&gt;
&lt;br /&gt;
gesucht: kürzester Weg, der alle Knoten genau einmal besucht (und zum Ausgangsknoten zurückkehrt)&lt;br /&gt;
&lt;br /&gt;
vorgegeben: Startknoten =&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 (v-1)!/2 mögliche Wege in einem vollständigen Graphen&lt;br /&gt;
&lt;br /&gt;
naive Lösung: brute force (Durchprobieren aller möglichen Pfade)&lt;br /&gt;
&lt;br /&gt;
'''Systematisches Erzeugen aller Permutationen'''&lt;br /&gt;
Trick: erzeuge jede Permutation in lexikographischer Ordnung&lt;br /&gt;
&lt;br /&gt;
 def next_permutation(a):&lt;br /&gt;
 	i = len(a) -1&lt;br /&gt;
 	while True:&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;
 	#lexicogr. Nachfolger hat groß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;
Komplexität: &amp;lt;math&amp;gt;(v-1)!&amp;lt;/math&amp;gt; Schleifendurchläufe, also &lt;br /&gt;
	&amp;lt;math&amp;gt;O(v!) = O(v^v)&amp;lt;/math&amp;gt;&lt;/div&gt;</summary>
		<author><name>Jschleic</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Graphen_und_Graphenalgorithmen&amp;diff=1862</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=1862"/>
				<updated>2008-06-25T22:42:56Z</updated>
		
		<summary type="html">&lt;p&gt;Jschleic: /* Algorithmus von Krushal */  nummerierung&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Einführung zu Graphen ==&lt;br /&gt;
&lt;br /&gt;
=== Motivation ===&lt;br /&gt;
&lt;br /&gt;
==== Königsberger - Brückenproblem ====&lt;br /&gt;
(1736 Euler)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Image:Koenigsberg.jpg]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Königsberger Brücken:&lt;br /&gt;
&lt;br /&gt;
Spaziergang durch Königsberg, so dass alle Brücken nur einmal überquert werden.&lt;br /&gt;
&lt;br /&gt;
Geometrie:&lt;br /&gt;
Topologie&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;
&lt;br /&gt;
* '''Definition: ungerichteter Graph'''&lt;br /&gt;
&lt;br /&gt;
Ein ungerichteter Graph G = ( V, E )&lt;br /&gt;
&lt;br /&gt;
** V ist endliche Menge von Knoten (vertices)&lt;br /&gt;
** E c V × V (edges)&lt;br /&gt;
&lt;br /&gt;
Ein Graph heißt ungerichtet, wenn zusätzlich gilt:&lt;br /&gt;
&lt;br /&gt;
(x,y) ∈ E =&amp;gt; (y,x) ∈ E (symmetrie)&lt;br /&gt;
&lt;br /&gt;
Bsp:&lt;br /&gt;
&lt;br /&gt;
gerichteter Graph&lt;br /&gt;
[[Image:digraph.png|gerichteter Graph]]&lt;br /&gt;
&lt;br /&gt;
 ungerichtet&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;
&lt;br /&gt;
   &lt;br /&gt;
&lt;br /&gt;
Bsp:&lt;br /&gt;
&lt;br /&gt;
* Landkarten:&lt;br /&gt;
** Knoten: Länder&lt;br /&gt;
** Kanten: gem. Grenzen&lt;br /&gt;
&lt;br /&gt;
* Schaltkreis:&lt;br /&gt;
** Knoten: Gatter&lt;br /&gt;
** Kanten: Verbindungen&lt;br /&gt;
&lt;br /&gt;
* Chemie (Summenformeln):&lt;br /&gt;
** Knoten: Elemente&lt;br /&gt;
** Kanten: Bindungen &lt;br /&gt;
&lt;br /&gt;
* Soziologie (StudieVZ)&lt;br /&gt;
** Soziogramm&lt;br /&gt;
*** Knoten: Personen&lt;br /&gt;
*** Kanten: Freund von ...&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* '''Definition: Vollständige Graphen'''&lt;br /&gt;
&lt;br /&gt;
Bei vollständigen Graphen ist jeder Knoten mit allen anderen Knoten verbunden.&lt;br /&gt;
&lt;br /&gt;
E =  U V (v,w) u (w,v)   |  v ∈ V, w ∈ V, u != w&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''&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?&lt;br /&gt;
&lt;br /&gt;
== Repräsentation von Graphen ==&lt;br /&gt;
&lt;br /&gt;
Sei G = ( V, E ) geg und liege V in einer lineraren Sortierung vor.&lt;br /&gt;
V = { v1, ...., vn }&lt;br /&gt;
&lt;br /&gt;
== Adjazenzmatrix ==&lt;br /&gt;
&lt;br /&gt;
AG = aij = {1 falls (vi, vj) ∈ E ; sonst 0}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Bsp:&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;
 AG = (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;
&lt;br /&gt;
== Adjezenzlisten ==&lt;br /&gt;
&lt;br /&gt;
al(v) = {v' ∈ V | (u,u') ∈ E}&lt;br /&gt;
Lg = ((v1, al(v1)), ...., (vn, al(vn))&lt;br /&gt;
&lt;br /&gt;
Python:&lt;br /&gt;
&lt;br /&gt;
 Array von Arrays [[...],[...],...,[...]]&lt;br /&gt;
                     0     1         n&lt;br /&gt;
&lt;br /&gt;
* '''Definition: Teilgraphen'''&lt;br /&gt;
&lt;br /&gt;
Ein Graph G' = (v',E') ist ein Teilgraph, wenn gilt:&lt;br /&gt;
&lt;br /&gt;
** v' c V &lt;br /&gt;
** E' c E &lt;br /&gt;
&lt;br /&gt;
Er heißt erzegender Graph, wenn zusätzlich gilt:&lt;br /&gt;
&lt;br /&gt;
** v' = V&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* '''Definition: Knotengrade'''&lt;br /&gt;
Für G = (v,E)und v ∈ V&lt;br /&gt;
grad(v) = |{v' ∈ V | v,v'∈ E}|&lt;br /&gt;
out_grad(v) = |   -&amp;quot;&amp;quot;-       |&lt;br /&gt;
in_grad(v)  = |{v'∈ V| (v',v) ∈ E}|&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Bsp: &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 ungerichtet&lt;br /&gt;
 &lt;br /&gt;
  c&lt;br /&gt;
 || \&lt;br /&gt;
 ||  \&lt;br /&gt;
  b   d          grad(a) = | {b,b,d} | = 3&lt;br /&gt;
 ||  /&lt;br /&gt;
 || /&lt;br /&gt;
  a&lt;br /&gt;
 &lt;br /&gt;
  &lt;br /&gt;
 gerichtet&lt;br /&gt;
 &lt;br /&gt;
  c←&lt;br /&gt;
  | \&lt;br /&gt;
  ↓  \&lt;br /&gt;
  b←--d         out_grad(d) = 2 = | {c,b} |&lt;br /&gt;
  |  /→          in_grad(d) = 1 = | {a} |&lt;br /&gt;
  ↓ /&lt;br /&gt;
  a&lt;br /&gt;
&lt;br /&gt;
* '''Definition: Wege'''&lt;br /&gt;
&lt;br /&gt;
Sei G = (v,E)&lt;br /&gt;
&lt;br /&gt;
** Für v0 ∈ V ist (v0) ein Weg in G&lt;br /&gt;
** Für Knoten v1,...vn,vn+1 und eine Kante (vn,vn+1) ∈ E ist mit einem Weg (v0,....vn) in G auch (v0,...,vn,vn+1) ein Weg in G.&lt;br /&gt;
&lt;br /&gt;
Also: Nichtleere Folgen von Knoten die durch eine Kante verbunden sind.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 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 == &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;
== Kreis == &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;
&lt;br /&gt;
== Zyklen ==&lt;br /&gt;
&lt;br /&gt;
    O&lt;br /&gt;
   /  \&lt;br /&gt;
  O    O&lt;br /&gt;
    \  |&lt;br /&gt;
     \ |   Wie Kreis nur ohne (vi != vj)&lt;br /&gt;
  O====O&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* '''Definition: planare Graphen'''&lt;br /&gt;
&lt;br /&gt;
Ist ein Graph, der auf einer Ebene gezeichnet werden ''kann'', sodass sich die Kanten nicht schneiden!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Bsp:&lt;br /&gt;
&lt;br /&gt;
 1)  &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;
 2)&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;
 3)&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 ''Regionen'' auch ausserhalb der Figur ist eine Region&lt;br /&gt;
 |@      |&lt;br /&gt;
 |-------|&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
1),2) und 3) sind planare Graphen.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Der K5 Graph ist kein planarer Graph da sich zwangsweise Kanten schneiden.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* '''Definition: dualer Graph'''&lt;br /&gt;
&lt;br /&gt;
Der duale Graph eines geg. planaren Graphs G' ist ein Graph mit&lt;br /&gt;
&lt;br /&gt;
** Knoten für jede Region&lt;br /&gt;
** Für jede Kante aus E gilt es gibt eine Kante, die die angrenzende Region mit Knoten verbindet.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 dualer Graph&lt;br /&gt;
&lt;br /&gt;
      O------O&lt;br /&gt;
      |     /| \&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;
&lt;br /&gt;
&lt;br /&gt;
* '''Definition: erreichbar'''&lt;br /&gt;
&lt;br /&gt;
 W ∈ V ist erreichbar von v ∈ G gdw.:&lt;br /&gt;
 es Existiert Weg(v,...w)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* '''Definition: Zusammenhang'''&lt;br /&gt;
&lt;br /&gt;
 G heißt zusammenhängend, wenn für Alle v,w ∈V gilt:&lt;br /&gt;
 w ist erreichbar von V&lt;br /&gt;
&lt;br /&gt;
== Bäume ==&lt;br /&gt;
&lt;br /&gt;
* '''Definition: Baum'''&lt;br /&gt;
&lt;br /&gt;
 Ein Baum ist ein zusammenhängender, kreisfreier Graph.&lt;br /&gt;
&lt;br /&gt;
Bsp.: Binary Search Tree&lt;br /&gt;
&lt;br /&gt;
* '''Definition: erzeugender Baum'''&lt;br /&gt;
&lt;br /&gt;
 für G = (v,E) ist ein erzeigender Teilgraph mit Baumeigenschaft&lt;br /&gt;
&lt;br /&gt;
Bsp.: &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    O    O&lt;br /&gt;
   /    /   &lt;br /&gt;
  O    O    O&lt;br /&gt;
  |  /    /   &lt;br /&gt;
  | /    /    &lt;br /&gt;
  O----O----O&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.get()&lt;br /&gt;
     node = q.get()&lt;br /&gt;
     for t in q[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;
&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;
&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;
        '''&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;
        '''&lt;br /&gt;
        def visit(graph, node, visited):&lt;br /&gt;
                '''&lt;br /&gt;
                visited ist Array mit Flags fuer besuchte Knoten&lt;br /&gt;
                '''&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;
&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 moeglicher 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;
* fuer ungerichtete Graphen gilt zusaetzlich: 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;
                '''&lt;br /&gt;
                anchor ist Anker der aktuellen ZK&lt;br /&gt;
                '''&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;
                '''&lt;br /&gt;
                Prueft auf anchors[k]==k&lt;br /&gt;
                '''&lt;br /&gt;
                while anchors[k] != k:&lt;br /&gt;
                        k = anchor[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;
                anchor[node] = findAnchor(anchors, node)&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;
&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;
&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;
&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;
&lt;br /&gt;
&lt;br /&gt;
===Minimaler Spannbaum (tree-MST)===&lt;br /&gt;
gegeben: gewichteter, zusammenhängender Graph G&lt;br /&gt;
gesucht: Untermenge E'\subseteq E, so dass \sum_{e\in E} w_e minimal und G' zusammenhängend&lt;br /&gt;
G' definiert dann einen Baum, denn andernfalls könnte man \sum verringern (eine Kante weglassen)&lt;br /&gt;
Anwendungen: Wie verbindet man n Punkte mit möglichst wenigen kurzen Straßen&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
====Algorithmus von Prim====&lt;br /&gt;
&lt;br /&gt;
 def prim(graph):&lt;br /&gt;
 	heap = []&lt;br /&gt;
 	visited = [False]*len(graph)&lt;br /&gt;
 	sum = 0&lt;br /&gt;
 	r = []&lt;br /&gt;
 	for neighbor in graph[0]:&lt;br /&gt;
 		heapq.heappush(heap, (neighbor[1], 0, neighbor[0]))&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&lt;br /&gt;
 		sum += wn&lt;br /&gt;
 		r.append([start, ziel])&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 Krushal====&lt;br /&gt;
Idee: wie beim Union-Find-Algorithmus für Zusammenhangskomponenten&lt;br /&gt;
# Behandle jeden Knoten als Baum für sich&lt;br /&gt;
# Fasse zwei Bäume zu neuem Baum zusammen&lt;br /&gt;
für MST: betrachte dazu die Kanten in aufsteigender Reihenfolge der Gewichte&lt;br /&gt;
(ignoriere Kanten zw. Knoten in gleichem Baum)&lt;br /&gt;
=&amp;gt; geeignet für Übungsaufgabe&lt;br /&gt;
&lt;br /&gt;
=== Problem des Handlungsreisenden (travelling salesman problem - TSP)===&lt;br /&gt;
gegeben: zusammenhängender, gewichteter Graph (oft vollständiger Graph)&lt;br /&gt;
&lt;br /&gt;
gesucht: kürzester Weg, der alle Knoten genau einmal besucht (und zum Ausgangsknoten zurückkehrt)&lt;br /&gt;
&lt;br /&gt;
vorgegeben: Startknoten =&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 (v-1)!/2 mögliche Wege in einem vollständigen Graphen&lt;br /&gt;
&lt;br /&gt;
naive Lösung: brute force (Durchprobieren aller möglichen Pfade)&lt;br /&gt;
&lt;br /&gt;
'''Systematisches Erzeugen aller Permutationen'''&lt;br /&gt;
Trick: erzeuge jede Permutation in lexikographischer Ordnung&lt;br /&gt;
&lt;br /&gt;
 def next_permutation(a):&lt;br /&gt;
 	i = len(a) -1&lt;br /&gt;
 	while True:&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;
 	#lexicogr. Nachfolger hat groß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;
Komplexität: &amp;lt;math&amp;gt;(v-1)!&amp;lt;/math&amp;gt; Schleifendurchläufe, also &lt;br /&gt;
	&amp;lt;math&amp;gt;O(v!) = O(v^v)&amp;lt;/math&amp;gt;&lt;/div&gt;</summary>
		<author><name>Jschleic</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Graphen_und_Graphenalgorithmen&amp;diff=1861</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=1861"/>
				<updated>2008-06-25T22:41:56Z</updated>
		
		<summary type="html">&lt;p&gt;Jschleic: /* Problem des Handlungsreisenden (travelling salesman problem - TSP) */  typo&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Einführung zu Graphen ==&lt;br /&gt;
&lt;br /&gt;
=== Motivation ===&lt;br /&gt;
&lt;br /&gt;
==== Königsberger - Brückenproblem ====&lt;br /&gt;
(1736 Euler)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Image:Koenigsberg.jpg]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Königsberger Brücken:&lt;br /&gt;
&lt;br /&gt;
Spaziergang durch Königsberg, so dass alle Brücken nur einmal überquert werden.&lt;br /&gt;
&lt;br /&gt;
Geometrie:&lt;br /&gt;
Topologie&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;
&lt;br /&gt;
* '''Definition: ungerichteter Graph'''&lt;br /&gt;
&lt;br /&gt;
Ein ungerichteter Graph G = ( V, E )&lt;br /&gt;
&lt;br /&gt;
** V ist endliche Menge von Knoten (vertices)&lt;br /&gt;
** E c V × V (edges)&lt;br /&gt;
&lt;br /&gt;
Ein Graph heißt ungerichtet, wenn zusätzlich gilt:&lt;br /&gt;
&lt;br /&gt;
(x,y) ∈ E =&amp;gt; (y,x) ∈ E (symmetrie)&lt;br /&gt;
&lt;br /&gt;
Bsp:&lt;br /&gt;
&lt;br /&gt;
gerichteter Graph&lt;br /&gt;
[[Image:digraph.png|gerichteter Graph]]&lt;br /&gt;
&lt;br /&gt;
 ungerichtet&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;
&lt;br /&gt;
   &lt;br /&gt;
&lt;br /&gt;
Bsp:&lt;br /&gt;
&lt;br /&gt;
* Landkarten:&lt;br /&gt;
** Knoten: Länder&lt;br /&gt;
** Kanten: gem. Grenzen&lt;br /&gt;
&lt;br /&gt;
* Schaltkreis:&lt;br /&gt;
** Knoten: Gatter&lt;br /&gt;
** Kanten: Verbindungen&lt;br /&gt;
&lt;br /&gt;
* Chemie (Summenformeln):&lt;br /&gt;
** Knoten: Elemente&lt;br /&gt;
** Kanten: Bindungen &lt;br /&gt;
&lt;br /&gt;
* Soziologie (StudieVZ)&lt;br /&gt;
** Soziogramm&lt;br /&gt;
*** Knoten: Personen&lt;br /&gt;
*** Kanten: Freund von ...&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* '''Definition: Vollständige Graphen'''&lt;br /&gt;
&lt;br /&gt;
Bei vollständigen Graphen ist jeder Knoten mit allen anderen Knoten verbunden.&lt;br /&gt;
&lt;br /&gt;
E =  U V (v,w) u (w,v)   |  v ∈ V, w ∈ V, u != w&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''&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?&lt;br /&gt;
&lt;br /&gt;
== Repräsentation von Graphen ==&lt;br /&gt;
&lt;br /&gt;
Sei G = ( V, E ) geg und liege V in einer lineraren Sortierung vor.&lt;br /&gt;
V = { v1, ...., vn }&lt;br /&gt;
&lt;br /&gt;
== Adjazenzmatrix ==&lt;br /&gt;
&lt;br /&gt;
AG = aij = {1 falls (vi, vj) ∈ E ; sonst 0}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Bsp:&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;
 AG = (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;
&lt;br /&gt;
== Adjezenzlisten ==&lt;br /&gt;
&lt;br /&gt;
al(v) = {v' ∈ V | (u,u') ∈ E}&lt;br /&gt;
Lg = ((v1, al(v1)), ...., (vn, al(vn))&lt;br /&gt;
&lt;br /&gt;
Python:&lt;br /&gt;
&lt;br /&gt;
 Array von Arrays [[...],[...],...,[...]]&lt;br /&gt;
                     0     1         n&lt;br /&gt;
&lt;br /&gt;
* '''Definition: Teilgraphen'''&lt;br /&gt;
&lt;br /&gt;
Ein Graph G' = (v',E') ist ein Teilgraph, wenn gilt:&lt;br /&gt;
&lt;br /&gt;
** v' c V &lt;br /&gt;
** E' c E &lt;br /&gt;
&lt;br /&gt;
Er heißt erzegender Graph, wenn zusätzlich gilt:&lt;br /&gt;
&lt;br /&gt;
** v' = V&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* '''Definition: Knotengrade'''&lt;br /&gt;
Für G = (v,E)und v ∈ V&lt;br /&gt;
grad(v) = |{v' ∈ V | v,v'∈ E}|&lt;br /&gt;
out_grad(v) = |   -&amp;quot;&amp;quot;-       |&lt;br /&gt;
in_grad(v)  = |{v'∈ V| (v',v) ∈ E}|&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Bsp: &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 ungerichtet&lt;br /&gt;
 &lt;br /&gt;
  c&lt;br /&gt;
 || \&lt;br /&gt;
 ||  \&lt;br /&gt;
  b   d          grad(a) = | {b,b,d} | = 3&lt;br /&gt;
 ||  /&lt;br /&gt;
 || /&lt;br /&gt;
  a&lt;br /&gt;
 &lt;br /&gt;
  &lt;br /&gt;
 gerichtet&lt;br /&gt;
 &lt;br /&gt;
  c←&lt;br /&gt;
  | \&lt;br /&gt;
  ↓  \&lt;br /&gt;
  b←--d         out_grad(d) = 2 = | {c,b} |&lt;br /&gt;
  |  /→          in_grad(d) = 1 = | {a} |&lt;br /&gt;
  ↓ /&lt;br /&gt;
  a&lt;br /&gt;
&lt;br /&gt;
* '''Definition: Wege'''&lt;br /&gt;
&lt;br /&gt;
Sei G = (v,E)&lt;br /&gt;
&lt;br /&gt;
** Für v0 ∈ V ist (v0) ein Weg in G&lt;br /&gt;
** Für Knoten v1,...vn,vn+1 und eine Kante (vn,vn+1) ∈ E ist mit einem Weg (v0,....vn) in G auch (v0,...,vn,vn+1) ein Weg in G.&lt;br /&gt;
&lt;br /&gt;
Also: Nichtleere Folgen von Knoten die durch eine Kante verbunden sind.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 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 == &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;
== Kreis == &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;
&lt;br /&gt;
== Zyklen ==&lt;br /&gt;
&lt;br /&gt;
    O&lt;br /&gt;
   /  \&lt;br /&gt;
  O    O&lt;br /&gt;
    \  |&lt;br /&gt;
     \ |   Wie Kreis nur ohne (vi != vj)&lt;br /&gt;
  O====O&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* '''Definition: planare Graphen'''&lt;br /&gt;
&lt;br /&gt;
Ist ein Graph, der auf einer Ebene gezeichnet werden ''kann'', sodass sich die Kanten nicht schneiden!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Bsp:&lt;br /&gt;
&lt;br /&gt;
 1)  &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;
 2)&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;
 3)&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 ''Regionen'' auch ausserhalb der Figur ist eine Region&lt;br /&gt;
 |@      |&lt;br /&gt;
 |-------|&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
1),2) und 3) sind planare Graphen.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Der K5 Graph ist kein planarer Graph da sich zwangsweise Kanten schneiden.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* '''Definition: dualer Graph'''&lt;br /&gt;
&lt;br /&gt;
Der duale Graph eines geg. planaren Graphs G' ist ein Graph mit&lt;br /&gt;
&lt;br /&gt;
** Knoten für jede Region&lt;br /&gt;
** Für jede Kante aus E gilt es gibt eine Kante, die die angrenzende Region mit Knoten verbindet.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 dualer Graph&lt;br /&gt;
&lt;br /&gt;
      O------O&lt;br /&gt;
      |     /| \&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;
&lt;br /&gt;
&lt;br /&gt;
* '''Definition: erreichbar'''&lt;br /&gt;
&lt;br /&gt;
 W ∈ V ist erreichbar von v ∈ G gdw.:&lt;br /&gt;
 es Existiert Weg(v,...w)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* '''Definition: Zusammenhang'''&lt;br /&gt;
&lt;br /&gt;
 G heißt zusammenhängend, wenn für Alle v,w ∈V gilt:&lt;br /&gt;
 w ist erreichbar von V&lt;br /&gt;
&lt;br /&gt;
== Bäume ==&lt;br /&gt;
&lt;br /&gt;
* '''Definition: Baum'''&lt;br /&gt;
&lt;br /&gt;
 Ein Baum ist ein zusammenhängender, kreisfreier Graph.&lt;br /&gt;
&lt;br /&gt;
Bsp.: Binary Search Tree&lt;br /&gt;
&lt;br /&gt;
* '''Definition: erzeugender Baum'''&lt;br /&gt;
&lt;br /&gt;
 für G = (v,E) ist ein erzeigender Teilgraph mit Baumeigenschaft&lt;br /&gt;
&lt;br /&gt;
Bsp.: &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    O    O&lt;br /&gt;
   /    /   &lt;br /&gt;
  O    O    O&lt;br /&gt;
  |  /    /   &lt;br /&gt;
  | /    /    &lt;br /&gt;
  O----O----O&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.get()&lt;br /&gt;
     node = q.get()&lt;br /&gt;
     for t in q[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;
&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;
&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;
        '''&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;
        '''&lt;br /&gt;
        def visit(graph, node, visited):&lt;br /&gt;
                '''&lt;br /&gt;
                visited ist Array mit Flags fuer besuchte Knoten&lt;br /&gt;
                '''&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;
&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 moeglicher 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;
* fuer ungerichtete Graphen gilt zusaetzlich: 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;
                '''&lt;br /&gt;
                anchor ist Anker der aktuellen ZK&lt;br /&gt;
                '''&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;
                '''&lt;br /&gt;
                Prueft auf anchors[k]==k&lt;br /&gt;
                '''&lt;br /&gt;
                while anchors[k] != k:&lt;br /&gt;
                        k = anchor[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;
                anchor[node] = findAnchor(anchors, node)&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;
&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;
&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;
&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;
&lt;br /&gt;
&lt;br /&gt;
===Minimaler Spannbaum (tree-MST)===&lt;br /&gt;
gegeben: gewichteter, zusammenhängender Graph G&lt;br /&gt;
gesucht: Untermenge E'\subseteq E, so dass \sum_{e\in E} w_e minimal und G' zusammenhängend&lt;br /&gt;
G' definiert dann einen Baum, denn andernfalls könnte man \sum verringern (eine Kante weglassen)&lt;br /&gt;
Anwendungen: Wie verbindet man n Punkte mit möglichst wenigen kurzen Straßen&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
====Algorithmus von Prim====&lt;br /&gt;
&lt;br /&gt;
 def prim(graph):&lt;br /&gt;
 	heap = []&lt;br /&gt;
 	visited = [False]*len(graph)&lt;br /&gt;
 	sum = 0&lt;br /&gt;
 	r = []&lt;br /&gt;
 	for neighbor in graph[0]:&lt;br /&gt;
 		heapq.heappush(heap, (neighbor[1], 0, neighbor[0]))&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&lt;br /&gt;
 		sum += wn&lt;br /&gt;
 		r.append([start, ziel])&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 Krushal====&lt;br /&gt;
Idee: wie beim Union-Find-Algorithmus für Zusammenhangskomponenten&lt;br /&gt;
1. Behandle jeden Knoten als Baum für sich&lt;br /&gt;
2. Fasse zwei Bäume zu neuem Baum zusammen&lt;br /&gt;
für MST: betrachte dazu die Kanten in aufsteigender Reihenfolge der Gewichte&lt;br /&gt;
(ignoriere Kanten zw. Knoten in gleichem Baum)&lt;br /&gt;
=&amp;gt; geeignet für Übungsaufgabe&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Problem des Handlungsreisenden (travelling salesman problem - TSP)===&lt;br /&gt;
gegeben: zusammenhängender, gewichteter Graph (oft vollständiger Graph)&lt;br /&gt;
&lt;br /&gt;
gesucht: kürzester Weg, der alle Knoten genau einmal besucht (und zum Ausgangsknoten zurückkehrt)&lt;br /&gt;
&lt;br /&gt;
vorgegeben: Startknoten =&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 (v-1)!/2 mögliche Wege in einem vollständigen Graphen&lt;br /&gt;
&lt;br /&gt;
naive Lösung: brute force (Durchprobieren aller möglichen Pfade)&lt;br /&gt;
&lt;br /&gt;
'''Systematisches Erzeugen aller Permutationen'''&lt;br /&gt;
Trick: erzeuge jede Permutation in lexikographischer Ordnung&lt;br /&gt;
&lt;br /&gt;
 def next_permutation(a):&lt;br /&gt;
 	i = len(a) -1&lt;br /&gt;
 	while True:&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;
 	#lexicogr. Nachfolger hat groß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;
Komplexität: &amp;lt;math&amp;gt;(v-1)!&amp;lt;/math&amp;gt; Schleifendurchläufe, also &lt;br /&gt;
	&amp;lt;math&amp;gt;O(v!) = O(v^v)&amp;lt;/math&amp;gt;&lt;/div&gt;</summary>
		<author><name>Jschleic</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Priorit%C3%A4tswarteschlangen&amp;diff=1860</id>
		<title>Prioritätswarteschlangen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Priorit%C3%A4tswarteschlangen&amp;diff=1860"/>
				<updated>2008-06-25T22:40:26Z</updated>
		
		<summary type="html">&lt;p&gt;Jschleic: /* Prioritätswarteschlangen */  typo&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Prioritätswarteschlangen==&lt;br /&gt;
&lt;br /&gt;
 * Max Priority Queue : insert(x)&lt;br /&gt;
                       x = largest()&lt;br /&gt;
                       removeLargest()&lt;br /&gt;
&lt;br /&gt;
 * Min Priority Queue: smallest()&lt;br /&gt;
                      removeSmallets()&lt;br /&gt;
===Prioritätswarteschlange als Suchproblem===&lt;br /&gt;
&lt;br /&gt;
*Sequentielle Suche - Array mit Prioritäten : insert(x)&amp;lt;=&amp;gt; a.append(x) (  &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;(amortisierte Komplexität))&lt;br /&gt;
&lt;br /&gt;
 def largest(a):&amp;lt;br&amp;gt;&lt;br /&gt;
    if len (a) == 0:  &amp;lt;br&amp;gt;&lt;br /&gt;
        raise RuntimeError(&amp;quot;...&amp;quot;)&amp;lt;br&amp;gt;&lt;br /&gt;
    ''' max = a[0]'''&amp;lt;br&amp;gt;&lt;br /&gt;
     '''k = 0&amp;lt;br&amp;gt;'''&lt;br /&gt;
     '''for n in range(1, len(a):'''             ''(innere Schleife von SelectionSort)''&amp;lt;br&amp;gt;&lt;br /&gt;
     '''   if a[n] &amp;gt; max'''                       (Das ganze hat die Komplexität:''' N = len(a) =&amp;gt;''' &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
        '''    max = a[n]'''&amp;lt;br&amp;gt;&lt;br /&gt;
             '''   k = n'''&amp;lt;br&amp;gt;&lt;br /&gt;
    ''' return k'''&amp;lt;br&amp;gt;&lt;br /&gt;
Bei kleine Array ist dies die schnellste Methode&lt;br /&gt;
&lt;br /&gt;
===Binärer (balancierter)Suchbaum===&lt;br /&gt;
insert =&amp;gt; z.B. wie beim Anderson Baum [http://hci.iwr.uni-heidelberg.de/alda/index.php/Suchen#Anderson-B.C3.A4ume] &amp;lt;math&amp;gt;\mathcal{O}(logN)&amp;lt;/math&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
 def largest(node):                     # Wurzel&amp;lt;br&amp;gt;&lt;br /&gt;
    '''if node.right is not None:'''    #rechts stehen immer  die großen&amp;lt;br&amp;gt;&lt;br /&gt;
        '''return largest(node.right)'''&amp;lt;br&amp;gt;&lt;br /&gt;
    '''return node'''                   #wenn es nicht mehr nach rechts geht, dann haben wir den größten Knoten gefunden&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Das ganze hat &amp;lt;math&amp;gt;\mathcal{O}(logN)&amp;lt;/math&amp;gt; Komplexität&amp;lt;br&amp;gt;&lt;br /&gt;
Ergebnis: gute Komplexität aber komplizierte Datenstruktur&lt;br /&gt;
===Heap===&lt;br /&gt;
*Datenstruktur optimiert für Prioritätssuche - man sucht nicht effizient  alle Knoten, sondern nur einen bestimmten z.B. Heap max&amp;lt;br&amp;gt;&lt;br /&gt;
*Definition: ein linkslastiger Binärbaum ist ein Baum mit &amp;lt;math&amp;gt;d(node.left) \geq d(node.right)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ein Heap ist ein linkslastiger, perfekt balancierter Baum.&lt;br /&gt;
(lässt sich max. um 1 unterscheiden)&lt;br /&gt;
&lt;br /&gt;
Man kann einen Heap leicht als Array implementieren, wie folgende Grafik veranschaulicht:&lt;br /&gt;
[[Image:heapArray.png|400px]]&lt;br /&gt;
&lt;br /&gt;
 '''index[parent] = [(indexChild - 1)/2]'''   #[] heißt abgerundet&amp;lt;br&amp;gt;&lt;br /&gt;
 '''index[left] = index[parent]2 + 1'''&amp;lt;br&amp;gt;&lt;br /&gt;
 '''index[right] = index[parent]2 + 2&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''=&amp;gt;''' linkslastiger perfect balancierter Binärbaum  kann effizient als Array abgespeichert werden&amp;lt;br&amp;gt;&lt;br /&gt;
'''=&amp;gt;'''verwende Indizes wie oben&lt;br /&gt;
====Heap - Bedingung====&lt;br /&gt;
* die Wurzel hat höhere Prioriät als die Kinder(gilt für jeden Teilbaum)&amp;lt;br&amp;gt;&lt;br /&gt;
'''=&amp;gt;'''Wurzel = array[0] hat die größte Priorität&amp;lt;br&amp;gt;&lt;br /&gt;
 def largest(h):&amp;lt;br&amp;gt;&lt;br /&gt;
     return h[0]        &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Einfügen in einem Heap====&lt;br /&gt;
&lt;br /&gt;
h - Array   #speichrt Haep&amp;lt;br&amp;gt;&lt;br /&gt;
  def insert(h,x):&amp;lt;br&amp;gt;&lt;br /&gt;
      h.append(x)  # wir speichern den Wurzel am Ende, so wird glecih zu einem linkslastigen perfect balancierten Baum (die Haep - Bedingung ist erfüllt)&amp;lt;br&amp;gt;&lt;br /&gt;
      upheap(h, len(h) - 1):&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 def upheap(h, k):&amp;lt;br&amp;gt;&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;k-tes Element evtl. an der falsche Stelle&amp;lt;br&amp;gt;&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;&amp;lt;br&amp;gt;&lt;br /&gt;
    v = h[k]&amp;lt;br&amp;gt;&lt;br /&gt;
    while True      #endlose Schleife&amp;lt;br&amp;gt;        &lt;br /&gt;
        if k == 0:&amp;lt;br&amp;gt;&lt;br /&gt;
            break&amp;lt;br&amp;gt;&lt;br /&gt;
        parent = (k - 1)/2&amp;lt;br&amp;gt;&lt;br /&gt;
        if h[parent]&amp;gt;v:&amp;lt;br&amp;gt;&lt;br /&gt;
            break&amp;lt;br&amp;gt;&lt;br /&gt;
        h[k] = h[parent]&amp;lt;br&amp;gt;&lt;br /&gt;
        k = parent&amp;lt;br&amp;gt;&lt;br /&gt;
    h[k] = v&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 def removeLargest(h):&lt;br /&gt;
     '''h[0] = h[len(h) - 1]'''&amp;lt;br&amp;gt;&lt;br /&gt;
     '''del h[len(n) - 1]'''              &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
     downHeap(h, 0)                       &amp;lt;math&amp;gt;\mathcal{O}(logN)&amp;lt;/math&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 def downHeap(h, k):&amp;lt;br&amp;gt;&lt;br /&gt;
     v = h[k]&amp;lt;br&amp;gt;&lt;br /&gt;
     while True&amp;lt;br&amp;gt;&lt;br /&gt;
         child = 2k + 1              #linke Kind&amp;lt;br&amp;gt;&lt;br /&gt;
         if child &amp;lt;math&amp;gt;\ge&amp;lt;/math&amp;gt;len(h):&amp;lt;br&amp;gt;&lt;br /&gt;
               break&amp;lt;br&amp;gt;&lt;br /&gt;
         if child &amp;lt; h[child] &amp;lt; h[child + 1]         #rechtes Kind&amp;lt;br&amp;gt;&lt;br /&gt;
               child = child + 1&lt;br /&gt;
         if v &amp;lt;math&amp;gt;\ge&amp;lt;/math&amp;gt; h[child]:&amp;lt;br&amp;gt;&lt;br /&gt;
               break&amp;lt;br&amp;gt;&lt;br /&gt;
         h[k] = h[child]&amp;lt;br&amp;gt;&lt;br /&gt;
         k = child&amp;lt;br&amp;gt;&lt;br /&gt;
    h[k] = v&lt;br /&gt;
&lt;br /&gt;
====Beispiel am Wort &amp;quot;SORTING&amp;quot;====&lt;br /&gt;
&lt;br /&gt;
(Grafiken folgen)&lt;br /&gt;
&lt;br /&gt;
====weitere Heapvarianten====&lt;br /&gt;
*Min-Max-Priority Queue (&amp;quot;Deap&amp;quot;, Double Ended Heap)&lt;br /&gt;
*Binomialer Heap, effiziente Operation &amp;quot;merege Heap&amp;quot;       &amp;lt;math&amp;gt;\mathcal{O}(n * log N (i*N_1 + N_2)&amp;lt;/math&amp;gt;&lt;br /&gt;
(Beweis durch binomiale Koeffizienten, Zusammenführen zweier Prioritätslisten)&lt;br /&gt;
*Fibonacci-Heap, einfügen in amortisierter Zeit      &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
(Beweis durch Fibonacci Zahlen)&lt;/div&gt;</summary>
		<author><name>Jschleic</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Main_Page&amp;diff=1859</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=1859"/>
				<updated>2008-06-25T21:43:31Z</updated>
		
		<summary type="html">&lt;p&gt;Jschleic: /* Übungsaufgaben */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Getting started ==&lt;br /&gt;
&lt;br /&gt;
* [http://meta.wikimedia.org/wiki/Help:Editing Wiki Editierhilfe] (Wiki-Syntax usw.)&lt;br /&gt;
* [http://www.mediawiki.org/wiki/Manual MediaWiki Manual] (für Administratoren)&lt;br /&gt;
* [[Sandbox]] (zum ungefährlichen Ausprobieren von Änderungen)&lt;br /&gt;
&lt;br /&gt;
== Vorlesung Algorithmen und Datenstrukturen ==&lt;br /&gt;
&lt;br /&gt;
Dr. Ullrich Köthe, Universität Heidelberg, Sommersemester 2008&lt;br /&gt;
&lt;br /&gt;
Die Vorlesung findet '''mittwochs''' um 11:15 Uhr in INF 227, HS 2 und '''donnerstags''' um 11:15 Uhr in INF 308, HS 2 statt. Die '''Abschlussklausur''' findet am Mittwoch, dem 23.7.2008 von 10:00 bis ca. 13:00 Uhr im HS1, INF 227 (KIP) statt. (Die genaue Klausurdauer wird noch bekannt gegeben. Hinweis: Sie benötigen einen Lichtbildausweis, um sich bei der Klausur zu indentifizieren!)&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. Im einzelnen können erworben werden:&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;
&lt;br /&gt;
=== Übungsbetrieb ===&lt;br /&gt;
* Termine der Übungsgruppen:&lt;br /&gt;
** Mo 11:00 - 13:00 Uhr, INF 350 (Otto-Meyerhof-Zentrum, Seiteneingang), Raum 014 (Tutor: Rahul Nair, [mailto:rnair(at)gmx(punkt)de rnair (at) gmx (punkt) de])&lt;br /&gt;
** Di 11:00 - 13:00 Uhr, INF 350 (Otto-Meyerhof-Zentrum, Seiteneingang), Raum 014 (Tutor: Thomas Gerlach, [mailto:gerlach@kip.uni-heidelberg.de gerlach@kip.uni-heidelberg.de])&lt;br /&gt;
** Mi 14:00 - 16:00 Uhr, '''neu: INF 327, Raum SR 5''' (Tutor: Christoph Sommer, [mailto:christoph.sommer@iwr.uni-heidelberg.de christoph.sommer@iwr.uni-heidelberg.de])  &lt;br /&gt;
** Do 14:00 - 16:00 Uhr, INF 294, Raum -113 (im Untergeschoss, Tutor: Daniel Kondermann, [mailto:daniel.kondermann@iwr.uni-heidelberg.de daniel.kondermann@iwr.uni-heidelberg.de])&lt;br /&gt;
* [[Main Page#Übungsaufgaben|Übungsaufgaben]] (Übungszettel mit Abgabetermin, Musterlösungen)&lt;br /&gt;
* [[Media:Punktestand.pdf|aktueller Punktestand]] (PDF, anonymisiert -- '''Immatrikulationsnummer 0 bedeutet, dass wir Ihre Nummer noch nicht haben, bitte nachreichen!''')&lt;br /&gt;
* Zur Klausur wird zugelassen, wer mindestens 60% der Übungspunkte erreicht. Außerdem muss jeder Teilnehmer eine Lösung (bzw. einen Teil davon) in der Übungsgruppe vorrechnen. Es gibt verschiedene Möglichkeiten, Zusatzpunkte zu erlangen (Bonusaufgaben, Anfertigung der Wiki-Seiten, gute Mitarbeit in den Übungen).&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)&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;
&lt;br /&gt;
([[Media:VL-Algorithmen-und-Datenstrukturen-ss08-Overview.pdf|Übersicht als PDF]] mit Übungsthemen)&lt;br /&gt;
&amp;lt;!-------------&amp;gt;&lt;br /&gt;
# [[Einführung]] (9.4.2008) &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]] (10.4.2008)&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]] (16. und 17.4.2008)&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]] (23. - 30.4.2008)&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]] (30.4. - 14.5.2008)&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]] (14. - 21.5.2008)&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]] (28.5.2008)&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]] (29.5.und 4.6.2008)&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]] (5.6.2008)&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]] (11.6.2008)&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]] (12. bis 19.6.2008)&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, mehrfacher 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;
&amp;lt;!-------------&amp;gt;&lt;br /&gt;
# [[Prinzipien des Algorithmenentwurfs]] (25.6.2008)&lt;br /&gt;
#* Repetition&lt;br /&gt;
#* Orthogonale Zerlegung des Problems&lt;br /&gt;
#* Hierarchische Zerlegung der Daten (Divide and Conquer)&lt;br /&gt;
#* Randomisierung&lt;br /&gt;
#* Optimierung, Zielfunktionen&lt;br /&gt;
#* Systematisierung von Algorithmen aus der bisherigen Vorlesung&lt;br /&gt;
&amp;lt;!-------------&amp;gt;&lt;br /&gt;
# [[Analytische Optimierung]] (25.6.2008)&lt;br /&gt;
#* Methode der kleinsten Quadrate&lt;br /&gt;
#* Approximation von Geraden&lt;br /&gt;
&amp;lt;!-------------&amp;gt;&lt;br /&gt;
# [[Randomisierte Algorithmen]] (26.6. und 2.7.2008)&lt;br /&gt;
#* Zufallszahlen, Zyklenlänge, Pitfalls&lt;br /&gt;
#* Zufallsverteilungen, Box-Muller Transformation&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 Integration, randomisierter Primzahltest &lt;br /&gt;
#* RANSAC-Algorithmus, Erfolgswahrscheinlichkeit&lt;br /&gt;
&amp;lt;!-------------&amp;gt;&lt;br /&gt;
# [[Greedy-Algorithmen]] (3.7.2008)&lt;br /&gt;
#* Prinzip&lt;br /&gt;
#* Bedingung für Optimalität&lt;br /&gt;
#* Beispiele für Greedy-Algorithmen&lt;br /&gt;
&amp;lt;!-------------&amp;gt;&lt;br /&gt;
# [[Dynamische Programmierung]] (9.7.2008)&lt;br /&gt;
#* Prinzip&lt;br /&gt;
#* Beispiele für Dynamische Programmierung&lt;br /&gt;
&amp;lt;!-------------&amp;gt;&lt;br /&gt;
# [[Erschöpfende Suche]] (10. und 16.7.2008)&lt;br /&gt;
#* Beispiele: u.a. Problem des Handlungsreisenden&lt;br /&gt;
#* Exponentielle Komplexität, NP-Vollständigkeit&lt;br /&gt;
#* Approximation bei NP-vollständigen Problemen&lt;br /&gt;
&amp;lt;!-------------&amp;gt;&lt;br /&gt;
# [[Quantum computing]] (17.7.2008)&lt;br /&gt;
&lt;br /&gt;
== Übungsaufgaben ==&lt;br /&gt;
&lt;br /&gt;
(im PDF Format). Die Abgabe erfolgt am angegebenen Tag bis 11: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 17.4.2008) und [[Media:Übung-1-Musterlösung.pdf|Musterlösung]]&lt;br /&gt;
#* Python-Tutorial&lt;br /&gt;
#* Sieb des Eratosthenes&lt;br /&gt;
#* Wert- und Referenzsemantik&lt;br /&gt;
&amp;lt;!--------------------&amp;gt;&lt;br /&gt;
# [[Media:Übung-2.pdf|Übung]] (Abgabe 24.4.2008) sowie Musterlösungen für [[Media:muster_blatt2-aufgabe1.pdf|Aufgabe 1]] und [[Media:muster_blatt2-aufgabe2.pdf|Aufgabe 2]]&lt;br /&gt;
#* Sortieren: Implementation und Geschwindigkeitsvergleich (Diagramme in Abhängigkeit von Problemgröße)&lt;br /&gt;
#* Entwicklung eines effizienten Algorithmus: Bruchfestigkeit von Gläsern&lt;br /&gt;
&amp;lt;!--------------------&amp;gt;&lt;br /&gt;
# [[Media:Übung-3.pdf|Übung]] ('''neuer Abgabetermin''' 7.5.2008) und [[Media:Übung-3-Musterlösung.pdf|Musterlösung]]&lt;br /&gt;
#* Experimente zur Effektivität von Unit Tests&lt;br /&gt;
#* Deque-Datenstruktur: Vor- und Nachbedingungen der Operationen, Implementation und Unit Tests&lt;br /&gt;
&amp;lt;!--------------------&amp;gt;&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;!--------------------&amp;gt;&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;!--------------------&amp;gt;&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;!--------------------&amp;gt;&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;!--------------------&amp;gt;&lt;br /&gt;
# [[Media:Übung-8.pdf|Übung]] (Abgabe 19.6.2008)&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;!--------------------&amp;gt;&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;span style=&amp;quot;color:red;&amp;quot;&amp;gt; '''Achtung: geänderte Version'''&amp;lt;/span&amp;gt; in der Originaldatei haben sich einige Fehler eingeschlichen. Hier ist eine neue Version der Entfernungen verfügbar: [http://www.rzuser.uni-heidelberg.de/~jschleic/entfernungen_neu.txt entfernungen_neu.txt]&lt;br /&gt;
&amp;lt;!--------------------&amp;gt;&lt;br /&gt;
# Übung (Abgabe 3.7.2008)&lt;br /&gt;
#* Beispiele für Divide and Conquer: pow-Funktion&lt;br /&gt;
#* Beispiel für Methode der kleinsten Quadrate: Approximation von Kreisen&lt;br /&gt;
&amp;lt;!--------------------&amp;gt;&lt;br /&gt;
# Übung (Abgabe 10.7.2008)&lt;br /&gt;
#* Randomisierte Algorithmen: Laufzeitvergleich deterministischer und randomisierter Primzahltest, RANSAC für Kreise&lt;br /&gt;
&amp;lt;!--------------------&amp;gt;&lt;br /&gt;
# Übung (Abgabe 17.7.2008)&lt;br /&gt;
#* Theoretische und praktische Aufgaben zur dynamische Programmierung&lt;br /&gt;
&amp;lt;!--------------------&amp;gt;&lt;br /&gt;
&amp;lt;!----# Übung (Abgabe 17.7.2008)---&amp;gt;&lt;br /&gt;
&amp;lt;!----#* Sudoku-Löser---&amp;gt;&lt;/div&gt;</summary>
		<author><name>Jschleic</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Main_Page&amp;diff=1858</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=1858"/>
				<updated>2008-06-25T21:41:28Z</updated>
		
		<summary type="html">&lt;p&gt;Jschleic: /* Übungsaufgaben */  Neue entfernungen.txt - im Original waren falsche Koordinaten bei Pforzheim und Oldenburg drin&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Getting started ==&lt;br /&gt;
&lt;br /&gt;
* [http://meta.wikimedia.org/wiki/Help:Editing Wiki Editierhilfe] (Wiki-Syntax usw.)&lt;br /&gt;
* [http://www.mediawiki.org/wiki/Manual MediaWiki Manual] (für Administratoren)&lt;br /&gt;
* [[Sandbox]] (zum ungefährlichen Ausprobieren von Änderungen)&lt;br /&gt;
&lt;br /&gt;
== Vorlesung Algorithmen und Datenstrukturen ==&lt;br /&gt;
&lt;br /&gt;
Dr. Ullrich Köthe, Universität Heidelberg, Sommersemester 2008&lt;br /&gt;
&lt;br /&gt;
Die Vorlesung findet '''mittwochs''' um 11:15 Uhr in INF 227, HS 2 und '''donnerstags''' um 11:15 Uhr in INF 308, HS 2 statt. Die '''Abschlussklausur''' findet am Mittwoch, dem 23.7.2008 von 10:00 bis ca. 13:00 Uhr im HS1, INF 227 (KIP) statt. (Die genaue Klausurdauer wird noch bekannt gegeben. Hinweis: Sie benötigen einen Lichtbildausweis, um sich bei der Klausur zu indentifizieren!)&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. Im einzelnen können erworben werden:&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;
&lt;br /&gt;
=== Übungsbetrieb ===&lt;br /&gt;
* Termine der Übungsgruppen:&lt;br /&gt;
** Mo 11:00 - 13:00 Uhr, INF 350 (Otto-Meyerhof-Zentrum, Seiteneingang), Raum 014 (Tutor: Rahul Nair, [mailto:rnair(at)gmx(punkt)de rnair (at) gmx (punkt) de])&lt;br /&gt;
** Di 11:00 - 13:00 Uhr, INF 350 (Otto-Meyerhof-Zentrum, Seiteneingang), Raum 014 (Tutor: Thomas Gerlach, [mailto:gerlach@kip.uni-heidelberg.de gerlach@kip.uni-heidelberg.de])&lt;br /&gt;
** Mi 14:00 - 16:00 Uhr, '''neu: INF 327, Raum SR 5''' (Tutor: Christoph Sommer, [mailto:christoph.sommer@iwr.uni-heidelberg.de christoph.sommer@iwr.uni-heidelberg.de])  &lt;br /&gt;
** Do 14:00 - 16:00 Uhr, INF 294, Raum -113 (im Untergeschoss, Tutor: Daniel Kondermann, [mailto:daniel.kondermann@iwr.uni-heidelberg.de daniel.kondermann@iwr.uni-heidelberg.de])&lt;br /&gt;
* [[Main Page#Übungsaufgaben|Übungsaufgaben]] (Übungszettel mit Abgabetermin, Musterlösungen)&lt;br /&gt;
* [[Media:Punktestand.pdf|aktueller Punktestand]] (PDF, anonymisiert -- '''Immatrikulationsnummer 0 bedeutet, dass wir Ihre Nummer noch nicht haben, bitte nachreichen!''')&lt;br /&gt;
* Zur Klausur wird zugelassen, wer mindestens 60% der Übungspunkte erreicht. Außerdem muss jeder Teilnehmer eine Lösung (bzw. einen Teil davon) in der Übungsgruppe vorrechnen. Es gibt verschiedene Möglichkeiten, Zusatzpunkte zu erlangen (Bonusaufgaben, Anfertigung der Wiki-Seiten, gute Mitarbeit in den Übungen).&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)&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;
&lt;br /&gt;
([[Media:VL-Algorithmen-und-Datenstrukturen-ss08-Overview.pdf|Übersicht als PDF]] mit Übungsthemen)&lt;br /&gt;
&amp;lt;!-------------&amp;gt;&lt;br /&gt;
# [[Einführung]] (9.4.2008) &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]] (10.4.2008)&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]] (16. und 17.4.2008)&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]] (23. - 30.4.2008)&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]] (30.4. - 14.5.2008)&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]] (14. - 21.5.2008)&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]] (28.5.2008)&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]] (29.5.und 4.6.2008)&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]] (5.6.2008)&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]] (11.6.2008)&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]] (12. bis 19.6.2008)&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, mehrfacher 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;
&amp;lt;!-------------&amp;gt;&lt;br /&gt;
# [[Prinzipien des Algorithmenentwurfs]] (25.6.2008)&lt;br /&gt;
#* Repetition&lt;br /&gt;
#* Orthogonale Zerlegung des Problems&lt;br /&gt;
#* Hierarchische Zerlegung der Daten (Divide and Conquer)&lt;br /&gt;
#* Randomisierung&lt;br /&gt;
#* Optimierung, Zielfunktionen&lt;br /&gt;
#* Systematisierung von Algorithmen aus der bisherigen Vorlesung&lt;br /&gt;
&amp;lt;!-------------&amp;gt;&lt;br /&gt;
# [[Analytische Optimierung]] (25.6.2008)&lt;br /&gt;
#* Methode der kleinsten Quadrate&lt;br /&gt;
#* Approximation von Geraden&lt;br /&gt;
&amp;lt;!-------------&amp;gt;&lt;br /&gt;
# [[Randomisierte Algorithmen]] (26.6. und 2.7.2008)&lt;br /&gt;
#* Zufallszahlen, Zyklenlänge, Pitfalls&lt;br /&gt;
#* Zufallsverteilungen, Box-Muller Transformation&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 Integration, randomisierter Primzahltest &lt;br /&gt;
#* RANSAC-Algorithmus, Erfolgswahrscheinlichkeit&lt;br /&gt;
&amp;lt;!-------------&amp;gt;&lt;br /&gt;
# [[Greedy-Algorithmen]] (3.7.2008)&lt;br /&gt;
#* Prinzip&lt;br /&gt;
#* Bedingung für Optimalität&lt;br /&gt;
#* Beispiele für Greedy-Algorithmen&lt;br /&gt;
&amp;lt;!-------------&amp;gt;&lt;br /&gt;
# [[Dynamische Programmierung]] (9.7.2008)&lt;br /&gt;
#* Prinzip&lt;br /&gt;
#* Beispiele für Dynamische Programmierung&lt;br /&gt;
&amp;lt;!-------------&amp;gt;&lt;br /&gt;
# [[Erschöpfende Suche]] (10. und 16.7.2008)&lt;br /&gt;
#* Beispiele: u.a. Problem des Handlungsreisenden&lt;br /&gt;
#* Exponentielle Komplexität, NP-Vollständigkeit&lt;br /&gt;
#* Approximation bei NP-vollständigen Problemen&lt;br /&gt;
&amp;lt;!-------------&amp;gt;&lt;br /&gt;
# [[Quantum computing]] (17.7.2008)&lt;br /&gt;
&lt;br /&gt;
== Übungsaufgaben ==&lt;br /&gt;
&lt;br /&gt;
(im PDF Format). Die Abgabe erfolgt am angegebenen Tag bis 11: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 17.4.2008) und [[Media:Übung-1-Musterlösung.pdf|Musterlösung]]&lt;br /&gt;
#* Python-Tutorial&lt;br /&gt;
#* Sieb des Eratosthenes&lt;br /&gt;
#* Wert- und Referenzsemantik&lt;br /&gt;
&amp;lt;!--------------------&amp;gt;&lt;br /&gt;
# [[Media:Übung-2.pdf|Übung]] (Abgabe 24.4.2008) sowie Musterlösungen für [[Media:muster_blatt2-aufgabe1.pdf|Aufgabe 1]] und [[Media:muster_blatt2-aufgabe2.pdf|Aufgabe 2]]&lt;br /&gt;
#* Sortieren: Implementation und Geschwindigkeitsvergleich (Diagramme in Abhängigkeit von Problemgröße)&lt;br /&gt;
#* Entwicklung eines effizienten Algorithmus: Bruchfestigkeit von Gläsern&lt;br /&gt;
&amp;lt;!--------------------&amp;gt;&lt;br /&gt;
# [[Media:Übung-3.pdf|Übung]] ('''neuer Abgabetermin''' 7.5.2008) und [[Media:Übung-3-Musterlösung.pdf|Musterlösung]]&lt;br /&gt;
#* Experimente zur Effektivität von Unit Tests&lt;br /&gt;
#* Deque-Datenstruktur: Vor- und Nachbedingungen der Operationen, Implementation und Unit Tests&lt;br /&gt;
&amp;lt;!--------------------&amp;gt;&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;!--------------------&amp;gt;&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;!--------------------&amp;gt;&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;!--------------------&amp;gt;&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;!--------------------&amp;gt;&lt;br /&gt;
# [[Media:Übung-8.pdf|Übung]] (Abgabe 19.6.2008)&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;!--------------------&amp;gt;&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;
#* '''Achtung: geänderte Version''' in der Originaldatei haben sich einige Fehler eingeschlichen. Hier ist eine neue Version der Entfernungen verfügbar: [http://www.rzuser.uni-heidelberg.de/~jschleic/entfernungen_neu.txt entfernungen_neu.txt]&lt;br /&gt;
&amp;lt;!--------------------&amp;gt;&lt;br /&gt;
# Übung (Abgabe 3.7.2008)&lt;br /&gt;
#* Beispiele für Divide and Conquer: pow-Funktion&lt;br /&gt;
#* Beispiel für Methode der kleinsten Quadrate: Approximation von Kreisen&lt;br /&gt;
&amp;lt;!--------------------&amp;gt;&lt;br /&gt;
# Übung (Abgabe 10.7.2008)&lt;br /&gt;
#* Randomisierte Algorithmen: Laufzeitvergleich deterministischer und randomisierter Primzahltest, RANSAC für Kreise&lt;br /&gt;
&amp;lt;!--------------------&amp;gt;&lt;br /&gt;
# Übung (Abgabe 17.7.2008)&lt;br /&gt;
#* Theoretische und praktische Aufgaben zur dynamische Programmierung&lt;br /&gt;
&amp;lt;!--------------------&amp;gt;&lt;br /&gt;
&amp;lt;!----# Übung (Abgabe 17.7.2008)---&amp;gt;&lt;br /&gt;
&amp;lt;!----#* Sudoku-Löser---&amp;gt;&lt;/div&gt;</summary>
		<author><name>Jschleic</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Graphen_und_Graphenalgorithmen&amp;diff=1857</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=1857"/>
				<updated>2008-06-25T21:33:26Z</updated>
		
		<summary type="html">&lt;p&gt;Jschleic: heutige Vorlesung (25.06.) vorläufig nicht-autorisiert hinzugefügt - Bitte vom Wiki-Beauftragten ergänzen / ersetzen!&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Einführung zu Graphen ==&lt;br /&gt;
&lt;br /&gt;
=== Motivation ===&lt;br /&gt;
&lt;br /&gt;
==== Königsberger - Brückenproblem ====&lt;br /&gt;
(1736 Euler)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Image:Koenigsberg.jpg]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Königsberger Brücken:&lt;br /&gt;
&lt;br /&gt;
Spaziergang durch Königsberg, so dass alle Brücken nur einmal überquert werden.&lt;br /&gt;
&lt;br /&gt;
Geometrie:&lt;br /&gt;
Topologie&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;
&lt;br /&gt;
* '''Definition: ungerichteter Graph'''&lt;br /&gt;
&lt;br /&gt;
Ein ungerichteter Graph G = ( V, E )&lt;br /&gt;
&lt;br /&gt;
** V ist endliche Menge von Knoten (vertices)&lt;br /&gt;
** E c V × V (edges)&lt;br /&gt;
&lt;br /&gt;
Ein Graph heißt ungerichtet, wenn zusätzlich gilt:&lt;br /&gt;
&lt;br /&gt;
(x,y) ∈ E =&amp;gt; (y,x) ∈ E (symmetrie)&lt;br /&gt;
&lt;br /&gt;
Bsp:&lt;br /&gt;
&lt;br /&gt;
gerichteter Graph&lt;br /&gt;
[[Image:digraph.png|gerichteter Graph]]&lt;br /&gt;
&lt;br /&gt;
 ungerichtet&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;
&lt;br /&gt;
   &lt;br /&gt;
&lt;br /&gt;
Bsp:&lt;br /&gt;
&lt;br /&gt;
* Landkarten:&lt;br /&gt;
** Knoten: Länder&lt;br /&gt;
** Kanten: gem. Grenzen&lt;br /&gt;
&lt;br /&gt;
* Schaltkreis:&lt;br /&gt;
** Knoten: Gatter&lt;br /&gt;
** Kanten: Verbindungen&lt;br /&gt;
&lt;br /&gt;
* Chemie (Summenformeln):&lt;br /&gt;
** Knoten: Elemente&lt;br /&gt;
** Kanten: Bindungen &lt;br /&gt;
&lt;br /&gt;
* Soziologie (StudieVZ)&lt;br /&gt;
** Soziogramm&lt;br /&gt;
*** Knoten: Personen&lt;br /&gt;
*** Kanten: Freund von ...&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* '''Definition: Vollständige Graphen'''&lt;br /&gt;
&lt;br /&gt;
Bei vollständigen Graphen ist jeder Knoten mit allen anderen Knoten verbunden.&lt;br /&gt;
&lt;br /&gt;
E =  U V (v,w) u (w,v)   |  v ∈ V, w ∈ V, u != w&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''&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?&lt;br /&gt;
&lt;br /&gt;
== Repräsentation von Graphen ==&lt;br /&gt;
&lt;br /&gt;
Sei G = ( V, E ) geg und liege V in einer lineraren Sortierung vor.&lt;br /&gt;
V = { v1, ...., vn }&lt;br /&gt;
&lt;br /&gt;
== Adjazenzmatrix ==&lt;br /&gt;
&lt;br /&gt;
AG = aij = {1 falls (vi, vj) ∈ E ; sonst 0}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Bsp:&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;
 AG = (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;
&lt;br /&gt;
== Adjezenzlisten ==&lt;br /&gt;
&lt;br /&gt;
al(v) = {v' ∈ V | (u,u') ∈ E}&lt;br /&gt;
Lg = ((v1, al(v1)), ...., (vn, al(vn))&lt;br /&gt;
&lt;br /&gt;
Python:&lt;br /&gt;
&lt;br /&gt;
 Array von Arrays [[...],[...],...,[...]]&lt;br /&gt;
                     0     1         n&lt;br /&gt;
&lt;br /&gt;
* '''Definition: Teilgraphen'''&lt;br /&gt;
&lt;br /&gt;
Ein Graph G' = (v',E') ist ein Teilgraph, wenn gilt:&lt;br /&gt;
&lt;br /&gt;
** v' c V &lt;br /&gt;
** E' c E &lt;br /&gt;
&lt;br /&gt;
Er heißt erzegender Graph, wenn zusätzlich gilt:&lt;br /&gt;
&lt;br /&gt;
** v' = V&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* '''Definition: Knotengrade'''&lt;br /&gt;
Für G = (v,E)und v ∈ V&lt;br /&gt;
grad(v) = |{v' ∈ V | v,v'∈ E}|&lt;br /&gt;
out_grad(v) = |   -&amp;quot;&amp;quot;-       |&lt;br /&gt;
in_grad(v)  = |{v'∈ V| (v',v) ∈ E}|&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Bsp: &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 ungerichtet&lt;br /&gt;
 &lt;br /&gt;
  c&lt;br /&gt;
 || \&lt;br /&gt;
 ||  \&lt;br /&gt;
  b   d          grad(a) = | {b,b,d} | = 3&lt;br /&gt;
 ||  /&lt;br /&gt;
 || /&lt;br /&gt;
  a&lt;br /&gt;
 &lt;br /&gt;
  &lt;br /&gt;
 gerichtet&lt;br /&gt;
 &lt;br /&gt;
  c←&lt;br /&gt;
  | \&lt;br /&gt;
  ↓  \&lt;br /&gt;
  b←--d         out_grad(d) = 2 = | {c,b} |&lt;br /&gt;
  |  /→          in_grad(d) = 1 = | {a} |&lt;br /&gt;
  ↓ /&lt;br /&gt;
  a&lt;br /&gt;
&lt;br /&gt;
* '''Definition: Wege'''&lt;br /&gt;
&lt;br /&gt;
Sei G = (v,E)&lt;br /&gt;
&lt;br /&gt;
** Für v0 ∈ V ist (v0) ein Weg in G&lt;br /&gt;
** Für Knoten v1,...vn,vn+1 und eine Kante (vn,vn+1) ∈ E ist mit einem Weg (v0,....vn) in G auch (v0,...,vn,vn+1) ein Weg in G.&lt;br /&gt;
&lt;br /&gt;
Also: Nichtleere Folgen von Knoten die durch eine Kante verbunden sind.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 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 == &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;
== Kreis == &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;
&lt;br /&gt;
== Zyklen ==&lt;br /&gt;
&lt;br /&gt;
    O&lt;br /&gt;
   /  \&lt;br /&gt;
  O    O&lt;br /&gt;
    \  |&lt;br /&gt;
     \ |   Wie Kreis nur ohne (vi != vj)&lt;br /&gt;
  O====O&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* '''Definition: planare Graphen'''&lt;br /&gt;
&lt;br /&gt;
Ist ein Graph, der auf einer Ebene gezeichnet werden ''kann'', sodass sich die Kanten nicht schneiden!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Bsp:&lt;br /&gt;
&lt;br /&gt;
 1)  &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;
 2)&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;
 3)&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 ''Regionen'' auch ausserhalb der Figur ist eine Region&lt;br /&gt;
 |@      |&lt;br /&gt;
 |-------|&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
1),2) und 3) sind planare Graphen.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Der K5 Graph ist kein planarer Graph da sich zwangsweise Kanten schneiden.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* '''Definition: dualer Graph'''&lt;br /&gt;
&lt;br /&gt;
Der duale Graph eines geg. planaren Graphs G' ist ein Graph mit&lt;br /&gt;
&lt;br /&gt;
** Knoten für jede Region&lt;br /&gt;
** Für jede Kante aus E gilt es gibt eine Kante, die die angrenzende Region mit Knoten verbindet.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 dualer Graph&lt;br /&gt;
&lt;br /&gt;
      O------O&lt;br /&gt;
      |     /| \&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;
&lt;br /&gt;
&lt;br /&gt;
* '''Definition: erreichbar'''&lt;br /&gt;
&lt;br /&gt;
 W ∈ V ist erreichbar von v ∈ G gdw.:&lt;br /&gt;
 es Existiert Weg(v,...w)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* '''Definition: Zusammenhang'''&lt;br /&gt;
&lt;br /&gt;
 G heißt zusammenhängend, wenn für Alle v,w ∈V gilt:&lt;br /&gt;
 w ist erreichbar von V&lt;br /&gt;
&lt;br /&gt;
== Bäume ==&lt;br /&gt;
&lt;br /&gt;
* '''Definition: Baum'''&lt;br /&gt;
&lt;br /&gt;
 Ein Baum ist ein zusammenhängender, kreisfreier Graph.&lt;br /&gt;
&lt;br /&gt;
Bsp.: Binary Search Tree&lt;br /&gt;
&lt;br /&gt;
* '''Definition: erzeugender Baum'''&lt;br /&gt;
&lt;br /&gt;
 für G = (v,E) ist ein erzeigender Teilgraph mit Baumeigenschaft&lt;br /&gt;
&lt;br /&gt;
Bsp.: &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    O    O&lt;br /&gt;
   /    /   &lt;br /&gt;
  O    O    O&lt;br /&gt;
  |  /    /   &lt;br /&gt;
  | /    /    &lt;br /&gt;
  O----O----O&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.get()&lt;br /&gt;
     node = q.get()&lt;br /&gt;
     for t in q[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;
&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;
&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;
        '''&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;
        '''&lt;br /&gt;
        def visit(graph, node, visited):&lt;br /&gt;
                '''&lt;br /&gt;
                visited ist Array mit Flags fuer besuchte Knoten&lt;br /&gt;
                '''&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;
&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 moeglicher 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;
* fuer ungerichtete Graphen gilt zusaetzlich: 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;
                '''&lt;br /&gt;
                anchor ist Anker der aktuellen ZK&lt;br /&gt;
                '''&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;
                '''&lt;br /&gt;
                Prueft auf anchors[k]==k&lt;br /&gt;
                '''&lt;br /&gt;
                while anchors[k] != k:&lt;br /&gt;
                        k = anchor[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;
                anchor[node] = findAnchor(anchors, node)&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;
&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;
&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;
&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;
&lt;br /&gt;
&lt;br /&gt;
===Minimaler Spannbaum (tree-MST)===&lt;br /&gt;
gegeben: gewichteter, zusammenhängender Graph G&lt;br /&gt;
gesucht: Untermenge E'\subseteq E, so dass \sum_{e\in E} w_e minimal und G' zusammenhängend&lt;br /&gt;
G' definiert dann einen Baum, denn andernfalls könnte man \sum verringern (eine Kante weglassen)&lt;br /&gt;
Anwendungen: Wie verbindet man n Punkte mit möglichst wenigen kurzen Straßen&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
====Algorithmus von Prim====&lt;br /&gt;
&lt;br /&gt;
 def prim(graph):&lt;br /&gt;
 	heap = []&lt;br /&gt;
 	visited = [False]*len(graph)&lt;br /&gt;
 	sum = 0&lt;br /&gt;
 	r = []&lt;br /&gt;
 	for neighbor in graph[0]:&lt;br /&gt;
 		heapq.heappush(heap, (neighbor[1], 0, neighbor[0]))&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&lt;br /&gt;
 		sum += wn&lt;br /&gt;
 		r.append([start, ziel])&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 Krushal====&lt;br /&gt;
Idee: wie beim Union-Find-Algorithmus für Zusammenhangskomponenten&lt;br /&gt;
1. Behandle jeden Knoten als Baum für sich&lt;br /&gt;
2. Fasse zwei Bäume zu neuem Baum zusammen&lt;br /&gt;
für MST: betrachte dazu die Kanten in aufsteigender Reihenfolge der Gewichte&lt;br /&gt;
(ignoriere Kanten zw. Knoten in gleichem Baum)&lt;br /&gt;
=&amp;gt; geeignet für Übungsaufgabe&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Problem des Handlungsreisenden (travelling salesman problem - TSP)===&lt;br /&gt;
gegeben: zusammenhängender, gewichteter Graph (oft vollständiger Graph)&lt;br /&gt;
gesucht: kürzester Weg, der alle Knoten genau einmal besucht (und zum Ausgangsknoten zurückkehrt)&lt;br /&gt;
vorgegeben: Startknoten =&amp;gt; v-1 Möglichkeiten =&amp;gt; je v-2 Möglichkeiten&lt;br /&gt;
also (v-1)!/2 mögliche Wege in einem vollständigen Graphen&lt;br /&gt;
&lt;br /&gt;
naive Lösung: brute force (Durchprobieren aller möglichen Pfade)&lt;br /&gt;
&lt;br /&gt;
'''Systematisches Erzeugen aller Permutationen'''&lt;br /&gt;
Trick: erzeuge jede Permutation in lexikographischer Ordnung&lt;br /&gt;
&lt;br /&gt;
 def next_permutation(a):&lt;br /&gt;
 	i = len(a) -1&lt;br /&gt;
 	while True:&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;
 	#lexicogr. Nachfolger hat groß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;
Komplexität: &amp;lt;math&amp;gt;(v-1)!&amp;lt;/math&amp;gt; Schleifendurchläufe, also &lt;br /&gt;
	O(v!) = O(v^v)&lt;/div&gt;</summary>
		<author><name>Jschleic</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Graphen_und_Graphenalgorithmen&amp;diff=1856</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=1856"/>
				<updated>2008-06-25T21:06:33Z</updated>
		
		<summary type="html">&lt;p&gt;Jschleic: /* Vereinfachte Lösung für den acyclic-Algorithmus */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Einführung zu Graphen ==&lt;br /&gt;
&lt;br /&gt;
=== Motivation ===&lt;br /&gt;
&lt;br /&gt;
==== Königsberger - Brückenproblem ====&lt;br /&gt;
(1736 Euler)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Image:Koenigsberg.jpg]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Königsberger Brücken:&lt;br /&gt;
&lt;br /&gt;
Spaziergang durch Königsberg, so dass alle Brücken nur einmal überquert werden.&lt;br /&gt;
&lt;br /&gt;
Geometrie:&lt;br /&gt;
Topologie&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;
&lt;br /&gt;
* '''Definition: ungerichteter Graph'''&lt;br /&gt;
&lt;br /&gt;
Ein ungerichteter Graph G = ( V, E )&lt;br /&gt;
&lt;br /&gt;
** V ist endliche Menge von Knoten (vertices)&lt;br /&gt;
** E c V × V (edges)&lt;br /&gt;
&lt;br /&gt;
Ein Graph heißt ungerichtet, wenn zusätzlich gilt:&lt;br /&gt;
&lt;br /&gt;
(x,y) ∈ E =&amp;gt; (y,x) ∈ E (symmetrie)&lt;br /&gt;
&lt;br /&gt;
Bsp:&lt;br /&gt;
&lt;br /&gt;
gerichteter Graph&lt;br /&gt;
[[Image:digraph.png|gerichteter Graph]]&lt;br /&gt;
&lt;br /&gt;
 ungerichtet&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;
&lt;br /&gt;
   &lt;br /&gt;
&lt;br /&gt;
Bsp:&lt;br /&gt;
&lt;br /&gt;
* Landkarten:&lt;br /&gt;
** Knoten: Länder&lt;br /&gt;
** Kanten: gem. Grenzen&lt;br /&gt;
&lt;br /&gt;
* Schaltkreis:&lt;br /&gt;
** Knoten: Gatter&lt;br /&gt;
** Kanten: Verbindungen&lt;br /&gt;
&lt;br /&gt;
* Chemie (Summenformeln):&lt;br /&gt;
** Knoten: Elemente&lt;br /&gt;
** Kanten: Bindungen &lt;br /&gt;
&lt;br /&gt;
* Soziologie (StudieVZ)&lt;br /&gt;
** Soziogramm&lt;br /&gt;
*** Knoten: Personen&lt;br /&gt;
*** Kanten: Freund von ...&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* '''Definition: Vollständige Graphen'''&lt;br /&gt;
&lt;br /&gt;
Bei vollständigen Graphen ist jeder Knoten mit allen anderen Knoten verbunden.&lt;br /&gt;
&lt;br /&gt;
E =  U V (v,w) u (w,v)   |  v ∈ V, w ∈ V, u != w&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''&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?&lt;br /&gt;
&lt;br /&gt;
== Repräsentation von Graphen ==&lt;br /&gt;
&lt;br /&gt;
Sei G = ( V, E ) geg und liege V in einer lineraren Sortierung vor.&lt;br /&gt;
V = { v1, ...., vn }&lt;br /&gt;
&lt;br /&gt;
== Adjazenzmatrix ==&lt;br /&gt;
&lt;br /&gt;
AG = aij = {1 falls (vi, vj) ∈ E ; sonst 0}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Bsp:&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;
 AG = (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;
&lt;br /&gt;
== Adjezenzlisten ==&lt;br /&gt;
&lt;br /&gt;
al(v) = {v' ∈ V | (u,u') ∈ E}&lt;br /&gt;
Lg = ((v1, al(v1)), ...., (vn, al(vn))&lt;br /&gt;
&lt;br /&gt;
Python:&lt;br /&gt;
&lt;br /&gt;
 Array von Arrays [[...],[...],...,[...]]&lt;br /&gt;
                     0     1         n&lt;br /&gt;
&lt;br /&gt;
* '''Definition: Teilgraphen'''&lt;br /&gt;
&lt;br /&gt;
Ein Graph G' = (v',E') ist ein Teilgraph, wenn gilt:&lt;br /&gt;
&lt;br /&gt;
** v' c V &lt;br /&gt;
** E' c E &lt;br /&gt;
&lt;br /&gt;
Er heißt erzegender Graph, wenn zusätzlich gilt:&lt;br /&gt;
&lt;br /&gt;
** v' = V&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* '''Definition: Knotengrade'''&lt;br /&gt;
Für G = (v,E)und v ∈ V&lt;br /&gt;
grad(v) = |{v' ∈ V | v,v'∈ E}|&lt;br /&gt;
out_grad(v) = |   -&amp;quot;&amp;quot;-       |&lt;br /&gt;
in_grad(v)  = |{v'∈ V| (v',v) ∈ E}|&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Bsp: &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 ungerichtet&lt;br /&gt;
 &lt;br /&gt;
  c&lt;br /&gt;
 || \&lt;br /&gt;
 ||  \&lt;br /&gt;
  b   d          grad(a) = | {b,b,d} | = 3&lt;br /&gt;
 ||  /&lt;br /&gt;
 || /&lt;br /&gt;
  a&lt;br /&gt;
 &lt;br /&gt;
  &lt;br /&gt;
 gerichtet&lt;br /&gt;
 &lt;br /&gt;
  c←&lt;br /&gt;
  | \&lt;br /&gt;
  ↓  \&lt;br /&gt;
  b←--d         out_grad(d) = 2 = | {c,b} |&lt;br /&gt;
  |  /→          in_grad(d) = 1 = | {a} |&lt;br /&gt;
  ↓ /&lt;br /&gt;
  a&lt;br /&gt;
&lt;br /&gt;
* '''Definition: Wege'''&lt;br /&gt;
&lt;br /&gt;
Sei G = (v,E)&lt;br /&gt;
&lt;br /&gt;
** Für v0 ∈ V ist (v0) ein Weg in G&lt;br /&gt;
** Für Knoten v1,...vn,vn+1 und eine Kante (vn,vn+1) ∈ E ist mit einem Weg (v0,....vn) in G auch (v0,...,vn,vn+1) ein Weg in G.&lt;br /&gt;
&lt;br /&gt;
Also: Nichtleere Folgen von Knoten die durch eine Kante verbunden sind.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 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 == &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;
== Kreis == &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;
&lt;br /&gt;
== Zyklen ==&lt;br /&gt;
&lt;br /&gt;
    O&lt;br /&gt;
   /  \&lt;br /&gt;
  O    O&lt;br /&gt;
    \  |&lt;br /&gt;
     \ |   Wie Kreis nur ohne (vi != vj)&lt;br /&gt;
  O====O&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* '''Definition: planare Graphen'''&lt;br /&gt;
&lt;br /&gt;
Ist ein Graph, der auf einer Ebene gezeichnet werden ''kann'', sodass sich die Kanten nicht schneiden!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Bsp:&lt;br /&gt;
&lt;br /&gt;
 1)  &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;
 2)&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;
 3)&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 ''Regionen'' auch ausserhalb der Figur ist eine Region&lt;br /&gt;
 |@      |&lt;br /&gt;
 |-------|&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
1),2) und 3) sind planare Graphen.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Der K5 Graph ist kein planarer Graph da sich zwangsweise Kanten schneiden.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* '''Definition: dualer Graph'''&lt;br /&gt;
&lt;br /&gt;
Der duale Graph eines geg. planaren Graphs G' ist ein Graph mit&lt;br /&gt;
&lt;br /&gt;
** Knoten für jede Region&lt;br /&gt;
** Für jede Kante aus E gilt es gibt eine Kante, die die angrenzende Region mit Knoten verbindet.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 dualer Graph&lt;br /&gt;
&lt;br /&gt;
      O------O&lt;br /&gt;
      |     /| \&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;
&lt;br /&gt;
&lt;br /&gt;
* '''Definition: erreichbar'''&lt;br /&gt;
&lt;br /&gt;
 W ∈ V ist erreichbar von v ∈ G gdw.:&lt;br /&gt;
 es Existiert Weg(v,...w)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* '''Definition: Zusammenhang'''&lt;br /&gt;
&lt;br /&gt;
 G heißt zusammenhängend, wenn für Alle v,w ∈V gilt:&lt;br /&gt;
 w ist erreichbar von V&lt;br /&gt;
&lt;br /&gt;
== Bäume ==&lt;br /&gt;
&lt;br /&gt;
* '''Definition: Baum'''&lt;br /&gt;
&lt;br /&gt;
 Ein Baum ist ein zusammenhängender, kreisfreier Graph.&lt;br /&gt;
&lt;br /&gt;
Bsp.: Binary Search Tree&lt;br /&gt;
&lt;br /&gt;
* '''Definition: erzeugender Baum'''&lt;br /&gt;
&lt;br /&gt;
 für G = (v,E) ist ein erzeigender Teilgraph mit Baumeigenschaft&lt;br /&gt;
&lt;br /&gt;
Bsp.: &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    O    O&lt;br /&gt;
   /    /   &lt;br /&gt;
  O    O    O&lt;br /&gt;
  |  /    /   &lt;br /&gt;
  | /    /    &lt;br /&gt;
  O----O----O&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.get()&lt;br /&gt;
     node = q.get()&lt;br /&gt;
     for t in q[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;
&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;
&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;
        '''&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;
        '''&lt;br /&gt;
        def visit(graph, node, visited):&lt;br /&gt;
                '''&lt;br /&gt;
                visited ist Array mit Flags fuer besuchte Knoten&lt;br /&gt;
                '''&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;
&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 moeglicher 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;
* fuer ungerichtete Graphen gilt zusaetzlich: 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;
                '''&lt;br /&gt;
                anchor ist Anker der aktuellen ZK&lt;br /&gt;
                '''&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;
                '''&lt;br /&gt;
                Prueft auf anchors[k]==k&lt;br /&gt;
                '''&lt;br /&gt;
                while anchors[k] != k:&lt;br /&gt;
                        k = anchor[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;
                anchor[node] = findAnchor(anchors, node)&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;
&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;
&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;
&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;/div&gt;</summary>
		<author><name>Jschleic</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Graphen_und_Graphenalgorithmen&amp;diff=1855</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=1855"/>
				<updated>2008-06-25T21:03:32Z</updated>
		
		<summary type="html">&lt;p&gt;Jschleic: /* Detektion von Zyklen */  fehlerhafter Code entfernt, funktionierender acyclic(graph) siehe unten&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Einführung zu Graphen ==&lt;br /&gt;
&lt;br /&gt;
=== Motivation ===&lt;br /&gt;
&lt;br /&gt;
==== Königsberger - Brückenproblem ====&lt;br /&gt;
(1736 Euler)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Image:Koenigsberg.jpg]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Königsberger Brücken:&lt;br /&gt;
&lt;br /&gt;
Spaziergang durch Königsberg, so dass alle Brücken nur einmal überquert werden.&lt;br /&gt;
&lt;br /&gt;
Geometrie:&lt;br /&gt;
Topologie&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;
&lt;br /&gt;
* '''Definition: ungerichteter Graph'''&lt;br /&gt;
&lt;br /&gt;
Ein ungerichteter Graph G = ( V, E )&lt;br /&gt;
&lt;br /&gt;
** V ist endliche Menge von Knoten (vertices)&lt;br /&gt;
** E c V × V (edges)&lt;br /&gt;
&lt;br /&gt;
Ein Graph heißt ungerichtet, wenn zusätzlich gilt:&lt;br /&gt;
&lt;br /&gt;
(x,y) ∈ E =&amp;gt; (y,x) ∈ E (symmetrie)&lt;br /&gt;
&lt;br /&gt;
Bsp:&lt;br /&gt;
&lt;br /&gt;
gerichteter Graph&lt;br /&gt;
[[Image:digraph.png|gerichteter Graph]]&lt;br /&gt;
&lt;br /&gt;
 ungerichtet&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;
&lt;br /&gt;
   &lt;br /&gt;
&lt;br /&gt;
Bsp:&lt;br /&gt;
&lt;br /&gt;
* Landkarten:&lt;br /&gt;
** Knoten: Länder&lt;br /&gt;
** Kanten: gem. Grenzen&lt;br /&gt;
&lt;br /&gt;
* Schaltkreis:&lt;br /&gt;
** Knoten: Gatter&lt;br /&gt;
** Kanten: Verbindungen&lt;br /&gt;
&lt;br /&gt;
* Chemie (Summenformeln):&lt;br /&gt;
** Knoten: Elemente&lt;br /&gt;
** Kanten: Bindungen &lt;br /&gt;
&lt;br /&gt;
* Soziologie (StudieVZ)&lt;br /&gt;
** Soziogramm&lt;br /&gt;
*** Knoten: Personen&lt;br /&gt;
*** Kanten: Freund von ...&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* '''Definition: Vollständige Graphen'''&lt;br /&gt;
&lt;br /&gt;
Bei vollständigen Graphen ist jeder Knoten mit allen anderen Knoten verbunden.&lt;br /&gt;
&lt;br /&gt;
E =  U V (v,w) u (w,v)   |  v ∈ V, w ∈ V, u != w&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''&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?&lt;br /&gt;
&lt;br /&gt;
== Repräsentation von Graphen ==&lt;br /&gt;
&lt;br /&gt;
Sei G = ( V, E ) geg und liege V in einer lineraren Sortierung vor.&lt;br /&gt;
V = { v1, ...., vn }&lt;br /&gt;
&lt;br /&gt;
== Adjazenzmatrix ==&lt;br /&gt;
&lt;br /&gt;
AG = aij = {1 falls (vi, vj) ∈ E ; sonst 0}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Bsp:&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;
 AG = (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;
&lt;br /&gt;
== Adjezenzlisten ==&lt;br /&gt;
&lt;br /&gt;
al(v) = {v' ∈ V | (u,u') ∈ E}&lt;br /&gt;
Lg = ((v1, al(v1)), ...., (vn, al(vn))&lt;br /&gt;
&lt;br /&gt;
Python:&lt;br /&gt;
&lt;br /&gt;
 Array von Arrays [[...],[...],...,[...]]&lt;br /&gt;
                     0     1         n&lt;br /&gt;
&lt;br /&gt;
* '''Definition: Teilgraphen'''&lt;br /&gt;
&lt;br /&gt;
Ein Graph G' = (v',E') ist ein Teilgraph, wenn gilt:&lt;br /&gt;
&lt;br /&gt;
** v' c V &lt;br /&gt;
** E' c E &lt;br /&gt;
&lt;br /&gt;
Er heißt erzegender Graph, wenn zusätzlich gilt:&lt;br /&gt;
&lt;br /&gt;
** v' = V&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* '''Definition: Knotengrade'''&lt;br /&gt;
Für G = (v,E)und v ∈ V&lt;br /&gt;
grad(v) = |{v' ∈ V | v,v'∈ E}|&lt;br /&gt;
out_grad(v) = |   -&amp;quot;&amp;quot;-       |&lt;br /&gt;
in_grad(v)  = |{v'∈ V| (v',v) ∈ E}|&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Bsp: &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 ungerichtet&lt;br /&gt;
 &lt;br /&gt;
  c&lt;br /&gt;
 || \&lt;br /&gt;
 ||  \&lt;br /&gt;
  b   d          grad(a) = | {b,b,d} | = 3&lt;br /&gt;
 ||  /&lt;br /&gt;
 || /&lt;br /&gt;
  a&lt;br /&gt;
 &lt;br /&gt;
  &lt;br /&gt;
 gerichtet&lt;br /&gt;
 &lt;br /&gt;
  c←&lt;br /&gt;
  | \&lt;br /&gt;
  ↓  \&lt;br /&gt;
  b←--d         out_grad(d) = 2 = | {c,b} |&lt;br /&gt;
  |  /→          in_grad(d) = 1 = | {a} |&lt;br /&gt;
  ↓ /&lt;br /&gt;
  a&lt;br /&gt;
&lt;br /&gt;
* '''Definition: Wege'''&lt;br /&gt;
&lt;br /&gt;
Sei G = (v,E)&lt;br /&gt;
&lt;br /&gt;
** Für v0 ∈ V ist (v0) ein Weg in G&lt;br /&gt;
** Für Knoten v1,...vn,vn+1 und eine Kante (vn,vn+1) ∈ E ist mit einem Weg (v0,....vn) in G auch (v0,...,vn,vn+1) ein Weg in G.&lt;br /&gt;
&lt;br /&gt;
Also: Nichtleere Folgen von Knoten die durch eine Kante verbunden sind.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 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 == &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;
== Kreis == &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;
&lt;br /&gt;
== Zyklen ==&lt;br /&gt;
&lt;br /&gt;
    O&lt;br /&gt;
   /  \&lt;br /&gt;
  O    O&lt;br /&gt;
    \  |&lt;br /&gt;
     \ |   Wie Kreis nur ohne (vi != vj)&lt;br /&gt;
  O====O&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* '''Definition: planare Graphen'''&lt;br /&gt;
&lt;br /&gt;
Ist ein Graph, der auf einer Ebene gezeichnet werden ''kann'', sodass sich die Kanten nicht schneiden!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Bsp:&lt;br /&gt;
&lt;br /&gt;
 1)  &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;
 2)&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;
 3)&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 ''Regionen'' auch ausserhalb der Figur ist eine Region&lt;br /&gt;
 |@      |&lt;br /&gt;
 |-------|&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
1),2) und 3) sind planare Graphen.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Der K5 Graph ist kein planarer Graph da sich zwangsweise Kanten schneiden.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* '''Definition: dualer Graph'''&lt;br /&gt;
&lt;br /&gt;
Der duale Graph eines geg. planaren Graphs G' ist ein Graph mit&lt;br /&gt;
&lt;br /&gt;
** Knoten für jede Region&lt;br /&gt;
** Für jede Kante aus E gilt es gibt eine Kante, die die angrenzende Region mit Knoten verbindet.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 dualer Graph&lt;br /&gt;
&lt;br /&gt;
      O------O&lt;br /&gt;
      |     /| \&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;
&lt;br /&gt;
&lt;br /&gt;
* '''Definition: erreichbar'''&lt;br /&gt;
&lt;br /&gt;
 W ∈ V ist erreichbar von v ∈ G gdw.:&lt;br /&gt;
 es Existiert Weg(v,...w)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* '''Definition: Zusammenhang'''&lt;br /&gt;
&lt;br /&gt;
 G heißt zusammenhängend, wenn für Alle v,w ∈V gilt:&lt;br /&gt;
 w ist erreichbar von V&lt;br /&gt;
&lt;br /&gt;
== Bäume ==&lt;br /&gt;
&lt;br /&gt;
* '''Definition: Baum'''&lt;br /&gt;
&lt;br /&gt;
 Ein Baum ist ein zusammenhängender, kreisfreier Graph.&lt;br /&gt;
&lt;br /&gt;
Bsp.: Binary Search Tree&lt;br /&gt;
&lt;br /&gt;
* '''Definition: erzeugender Baum'''&lt;br /&gt;
&lt;br /&gt;
 für G = (v,E) ist ein erzeigender Teilgraph mit Baumeigenschaft&lt;br /&gt;
&lt;br /&gt;
Bsp.: &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    O    O&lt;br /&gt;
   /    /   &lt;br /&gt;
  O    O    O&lt;br /&gt;
  |  /    /   &lt;br /&gt;
  | /    /    &lt;br /&gt;
  O----O----O&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.get()&lt;br /&gt;
     node = q.get()&lt;br /&gt;
     for t in q[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;
&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;
&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;
        '''&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;
        '''&lt;br /&gt;
        def visit(graph, node, visited):&lt;br /&gt;
                '''&lt;br /&gt;
                visited ist Array mit Flags fuer besuchte Knoten&lt;br /&gt;
                '''&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;
&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 moeglicher 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;
* fuer ungerichtete Graphen gilt zusaetzlich: 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;
                '''&lt;br /&gt;
                anchor ist Anker der aktuellen ZK&lt;br /&gt;
                '''&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;
                '''&lt;br /&gt;
                Prueft auf anchors[k]==k&lt;br /&gt;
                '''&lt;br /&gt;
                while anchors[k] != k:&lt;br /&gt;
                        k = anchor[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;
                anchor[node] = findAnchor(anchors, node)&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;
&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;
&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;
&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;
&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;
&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;/div&gt;</summary>
		<author><name>Jschleic</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Graphen_und_Graphenalgorithmen&amp;diff=1847</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=1847"/>
				<updated>2008-06-25T09:08:24Z</updated>
		
		<summary type="html">&lt;p&gt;Jschleic: /* Detektion von Zyklen */  acyclic2&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Einführung zu Graphen ==&lt;br /&gt;
&lt;br /&gt;
=== Motivation ===&lt;br /&gt;
&lt;br /&gt;
==== Königsberger - Brückenproblem ====&lt;br /&gt;
(1736 Euler)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Image:Koenigsberg.jpg]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Königsberger Brücken:&lt;br /&gt;
&lt;br /&gt;
Spaziergang durch Königsberg, so dass alle Brücken nur einmal überquert werden.&lt;br /&gt;
&lt;br /&gt;
Geometrie:&lt;br /&gt;
Topologie&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;
&lt;br /&gt;
* '''Definition: ungerichteter Graph'''&lt;br /&gt;
&lt;br /&gt;
Ein ungerichteter Graph G = ( V, E )&lt;br /&gt;
&lt;br /&gt;
** V ist endliche Menge von Knoten (vertices)&lt;br /&gt;
** E c V × V (edges)&lt;br /&gt;
&lt;br /&gt;
Ein Graph heißt ungerichtet, wenn zusätzlich gilt:&lt;br /&gt;
&lt;br /&gt;
(x,y) ∈ E =&amp;gt; (y,x) ∈ E (symmetrie)&lt;br /&gt;
&lt;br /&gt;
Bsp:&lt;br /&gt;
&lt;br /&gt;
gerichteter Graph&lt;br /&gt;
[[Image:digraph.png|gerichteter Graph]]&lt;br /&gt;
&lt;br /&gt;
 ungerichtet&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;
&lt;br /&gt;
   &lt;br /&gt;
&lt;br /&gt;
Bsp:&lt;br /&gt;
&lt;br /&gt;
* Landkarten:&lt;br /&gt;
** Knoten: Länder&lt;br /&gt;
** Kanten: gem. Grenzen&lt;br /&gt;
&lt;br /&gt;
* Schaltkreis:&lt;br /&gt;
** Knoten: Gatter&lt;br /&gt;
** Kanten: Verbindungen&lt;br /&gt;
&lt;br /&gt;
* Chemie (Summenformeln):&lt;br /&gt;
** Knoten: Elemente&lt;br /&gt;
** Kanten: Bindungen &lt;br /&gt;
&lt;br /&gt;
* Soziologie (StudieVZ)&lt;br /&gt;
** Soziogramm&lt;br /&gt;
*** Knoten: Personen&lt;br /&gt;
*** Kanten: Freund von ...&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* '''Definition: Vollständige Graphen'''&lt;br /&gt;
&lt;br /&gt;
Bei vollständigen Graphen ist jeder Knoten mit allen anderen Knoten verbunden.&lt;br /&gt;
&lt;br /&gt;
E =  U V (v,w) u (w,v)   |  v ∈ V, w ∈ V, u != w&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''&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?&lt;br /&gt;
&lt;br /&gt;
== Repräsentation von Graphen ==&lt;br /&gt;
&lt;br /&gt;
Sei G = ( V, E ) geg und liege V in einer lineraren Sortierung vor.&lt;br /&gt;
V = { v1, ...., vn }&lt;br /&gt;
&lt;br /&gt;
== Adjazenzmatrix ==&lt;br /&gt;
&lt;br /&gt;
AG = aij = {1 falls (vi, vj) ∈ E ; sonst 0}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Bsp:&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;
 AG = (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;
&lt;br /&gt;
== Adjezenzlisten ==&lt;br /&gt;
&lt;br /&gt;
al(v) = {v' ∈ V | (u,u') ∈ E}&lt;br /&gt;
Lg = ((v1, al(v1)), ...., (vn, al(vn))&lt;br /&gt;
&lt;br /&gt;
Python:&lt;br /&gt;
&lt;br /&gt;
 Array von Arrays [[...],[...],...,[...]]&lt;br /&gt;
                     0     1         n&lt;br /&gt;
&lt;br /&gt;
* '''Definition: Teilgraphen'''&lt;br /&gt;
&lt;br /&gt;
Ein Graph G' = (v',E') ist ein Teilgraph, wenn gilt:&lt;br /&gt;
&lt;br /&gt;
** v' c V &lt;br /&gt;
** E' c E &lt;br /&gt;
&lt;br /&gt;
Er heißt erzegender Graph, wenn zusätzlich gilt:&lt;br /&gt;
&lt;br /&gt;
** v' = V&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* '''Definition: Knotengrade'''&lt;br /&gt;
Für G = (v,E)und v ∈ V&lt;br /&gt;
grad(v) = |{v' ∈ V | v,v'∈ E}|&lt;br /&gt;
out_grad(v) = |   -&amp;quot;&amp;quot;-       |&lt;br /&gt;
in_grad(v)  = |{v'∈ V| (v',v) ∈ E}|&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Bsp: &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 ungerichtet&lt;br /&gt;
 &lt;br /&gt;
  c&lt;br /&gt;
 || \&lt;br /&gt;
 ||  \&lt;br /&gt;
  b   d          grad(a) = | {b,b,d} | = 3&lt;br /&gt;
 ||  /&lt;br /&gt;
 || /&lt;br /&gt;
  a&lt;br /&gt;
 &lt;br /&gt;
  &lt;br /&gt;
 gerichtet&lt;br /&gt;
 &lt;br /&gt;
  c←&lt;br /&gt;
  | \&lt;br /&gt;
  ↓  \&lt;br /&gt;
  b←--d         out_grad(d) = 2 = | {c,b} |&lt;br /&gt;
  |  /→          in_grad(d) = 1 = | {a} |&lt;br /&gt;
  ↓ /&lt;br /&gt;
  a&lt;br /&gt;
&lt;br /&gt;
* '''Definition: Wege'''&lt;br /&gt;
&lt;br /&gt;
Sei G = (v,E)&lt;br /&gt;
&lt;br /&gt;
** Für v0 ∈ V ist (v0) ein Weg in G&lt;br /&gt;
** Für Knoten v1,...vn,vn+1 und eine Kante (vn,vn+1) ∈ E ist mit einem Weg (v0,....vn) in G auch (v0,...,vn,vn+1) ein Weg in G.&lt;br /&gt;
&lt;br /&gt;
Also: Nichtleere Folgen von Knoten die durch eine Kante verbunden sind.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 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 == &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;
== Kreis == &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;
&lt;br /&gt;
== Zyklen ==&lt;br /&gt;
&lt;br /&gt;
    O&lt;br /&gt;
   /  \&lt;br /&gt;
  O    O&lt;br /&gt;
    \  |&lt;br /&gt;
     \ |   Wie Kreis nur ohne (vi != vj)&lt;br /&gt;
  O====O&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* '''Definition: planare Graphen'''&lt;br /&gt;
&lt;br /&gt;
Ist ein Graph, der auf einer Ebene gezeichnet werden ''kann'', sodass sich die Kanten nicht schneiden!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Bsp:&lt;br /&gt;
&lt;br /&gt;
 1)  &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;
 2)&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;
 3)&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 ''Regionen'' auch ausserhalb der Figur ist eine Region&lt;br /&gt;
 |@      |&lt;br /&gt;
 |-------|&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
1),2) und 3) sind planare Graphen.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Der K5 Graph ist kein planarer Graph da sich zwangsweise Kanten schneiden.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* '''Definition: dualer Graph'''&lt;br /&gt;
&lt;br /&gt;
Der duale Graph eines geg. planaren Graphs G' ist ein Graph mit&lt;br /&gt;
&lt;br /&gt;
** Knoten für jede Region&lt;br /&gt;
** Für jede Kante aus E gilt es gibt eine Kante, die die angrenzende Region mit Knoten verbindet.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 dualer Graph&lt;br /&gt;
&lt;br /&gt;
      O------O&lt;br /&gt;
      |     /| \&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;
&lt;br /&gt;
&lt;br /&gt;
* '''Definition: erreichbar'''&lt;br /&gt;
&lt;br /&gt;
 W ∈ V ist erreichbar von v ∈ G gdw.:&lt;br /&gt;
 es Existiert Weg(v,...w)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* '''Definition: Zusammenhang'''&lt;br /&gt;
&lt;br /&gt;
 G heißt zusammenhängend, wenn für Alle v,w ∈V gilt:&lt;br /&gt;
 w ist erreichbar von V&lt;br /&gt;
&lt;br /&gt;
== Bäume ==&lt;br /&gt;
&lt;br /&gt;
* '''Definition: Baum'''&lt;br /&gt;
&lt;br /&gt;
 Ein Baum ist ein zusammenhängender, kreisfreier Graph.&lt;br /&gt;
&lt;br /&gt;
Bsp.: Binary Search Tree&lt;br /&gt;
&lt;br /&gt;
* '''Definition: erzeugender Baum'''&lt;br /&gt;
&lt;br /&gt;
 für G = (v,E) ist ein erzeigender Teilgraph mit Baumeigenschaft&lt;br /&gt;
&lt;br /&gt;
Bsp.: &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    O    O&lt;br /&gt;
   /    /   &lt;br /&gt;
  O    O    O&lt;br /&gt;
  |  /    /   &lt;br /&gt;
  | /    /    &lt;br /&gt;
  O----O----O&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.get()&lt;br /&gt;
     node = q.get()&lt;br /&gt;
     for t in q[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;
&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;
&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;
        '''&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;
        '''&lt;br /&gt;
        def visit(graph, node, visited):&lt;br /&gt;
                '''&lt;br /&gt;
                visited ist Array mit Flags fuer besuchte Knoten&lt;br /&gt;
                '''&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;
&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 moeglicher 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;
* fuer ungerichtete Graphen gilt zusaetzlich: 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;
                '''&lt;br /&gt;
                anchor ist Anker der aktuellen ZK&lt;br /&gt;
                '''&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;
                '''&lt;br /&gt;
                Prueft auf anchors[k]==k&lt;br /&gt;
                '''&lt;br /&gt;
                while anchors[k] != k:&lt;br /&gt;
                        k = anchor[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;
                anchor[node] = findAnchor(anchors, node)&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;
=== Detektion von Zyklen ===&lt;br /&gt;
&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: diesmal wird die Reihenfolge, in der&lt;br /&gt;
die Knoten gefunden werden gespeichert. Es gibt einen Zyklus genau dann, wenn man zu&lt;br /&gt;
einem hinreichend frueher (d.h. nicht zum direkten Vorgaenger) Knoten zurueckkommt.&lt;br /&gt;
&lt;br /&gt;
* Beispiel: ...&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 def acyclicGraph(graph):               # True, falls keine Zyklen bestehen&lt;br /&gt;
        def visit(graph, node, visited, count):&lt;br /&gt;
                '''&lt;br /&gt;
                muss gewaehrleisten, dass &amp;lt;visited[node]&amp;gt; den kleinsten&lt;br /&gt;
                  von &amp;lt;node&amp;gt; aus mit Tiefensuche erreichbaren Knoten&lt;br /&gt;
                  angibt&lt;br /&gt;
                '''&lt;br /&gt;
                visited[node] = count&lt;br /&gt;
                count += 1&lt;br /&gt;
                minVal = visited[node]&lt;br /&gt;
                for neighbor in graph[node]:&lt;br /&gt;
                        if visited[neighbor] is None:&lt;br /&gt;
                                count, minRes = visit(graph, neighbor, visited, count)&lt;br /&gt;
                                # minRes ist der kleinste in diesem Aufruf gefundene Knoten&lt;br /&gt;
                                if minRes &amp;lt; minVal:&lt;br /&gt;
                                        minVal = minRes&lt;br /&gt;
                        elif visited[neighbor] &amp;lt; minVal:&lt;br /&gt;
                                minVal = visited[neighbor]&lt;br /&gt;
                return count, minVal&lt;br /&gt;
&lt;br /&gt;
        visited = [None]*len(graph)&lt;br /&gt;
        count = 0                       # Zaehler fuer Reihenfolge&lt;br /&gt;
        for node in range(len(graph)):&lt;br /&gt;
                if visited[node] is not None:&lt;br /&gt;
                        continue&lt;br /&gt;
                count, minVal = visit(graph, node, visited, count)&lt;br /&gt;
        for node in range(len(graph)):&lt;br /&gt;
                if visited[node] &amp;lt; node: return False # Zyklus&lt;br /&gt;
        return True&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
2. Variante von acyclic:&lt;br /&gt;
 def acyclic2(graph):&lt;br /&gt;
    visited = [False]*len(graph)&lt;br /&gt;
    for node in range(len(graph)):&lt;br /&gt;
        if visited[node]:continue #Kein Zyklus&lt;br /&gt;
        if not visit2(graph, node, None, visited): return False&lt;br /&gt;
    return True&lt;br /&gt;
 &lt;br /&gt;
 def visit2(graph, node, fromNode, visited):&lt;br /&gt;
    if visited[node]: return False #Zyklus entdeckt&lt;br /&gt;
    visited[node] = True #Knoten wird markiert&lt;br /&gt;
    for neighbor in graph[node]:&lt;br /&gt;
        if neighbor == fromNode: continue #Ueberspringen von fromNode&lt;br /&gt;
        if not visit2(graph, neighbor, node, visited): return False #Zyklus weitergeben&lt;br /&gt;
    return True #kein Zyklus&lt;/div&gt;</summary>
		<author><name>Jschleic</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Sandbox&amp;diff=1839</id>
		<title>Sandbox</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Sandbox&amp;diff=1839"/>
				<updated>2008-06-23T21:08:37Z</updated>
		
		<summary type="html">&lt;p&gt;Jschleic: test TeX&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Formel:&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\Delta L_{sum} = \frac{\sqrt{L_R^4 \cdot \Delta L_B^2 + L_B^4 \cdot \Delta L_R^2}}{(L_B + L_R)^2}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
---&lt;br /&gt;
&amp;lt;math&amp;gt;f(x)=\sqrt[4]{x}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f(x)=\frac{y}{x}&amp;lt;/math&amp;gt; &amp;lt;br/&amp;gt;&lt;br /&gt;
'''&lt;br /&gt;
hallo'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
There was a young lady from Riga,&lt;br /&gt;
&lt;br /&gt;
who smiled as she rode on a tiger.&lt;br /&gt;
&lt;br /&gt;
they returned from the ride&lt;br /&gt;
&lt;br /&gt;
with the lady inside&lt;br /&gt;
&lt;br /&gt;
and the smile on the face of the tiger.&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;0&amp;quot; cellpadding=&amp;quot;9&amp;quot; align=&amp;quot;center&amp;quot;&lt;br /&gt;
 ! hello&lt;br /&gt;
! is&lt;br /&gt;
|- &lt;br /&gt;
| a&lt;br /&gt;
| table&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
          5&lt;br /&gt;
&lt;br /&gt;
       /       \&lt;br /&gt;
&lt;br /&gt;
     2           8&lt;br /&gt;
&lt;br /&gt;
   /   \       /   \&lt;br /&gt;
&lt;br /&gt;
 1       4   7       9&lt;br /&gt;
&lt;br /&gt;
This is a sentence.&lt;/div&gt;</summary>
		<author><name>Jschleic</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Sortieren&amp;diff=1825</id>
		<title>Sortieren</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Sortieren&amp;diff=1825"/>
				<updated>2008-06-20T13:16:25Z</updated>
		
		<summary type="html">&lt;p&gt;Jschleic: /* a.1) Algorithmus c ← merge(a,b) */  Code als ein block&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;----&lt;br /&gt;
== Laufzeitmesung in Python ==&lt;br /&gt;
&lt;br /&gt;
Verwendung der '''timeit-Bibliothek''' für die Hausaufgabe. &lt;br /&gt;
&lt;br /&gt;
* Importiere das timeit-Modul: &amp;lt;tt&amp;gt;import timeit&amp;lt;/tt&amp;gt;&lt;br /&gt;
* Teile den Algorithmus in die Initialisierungen und den Teil, dessen Geschwindigkeit gemessen werden soll. Beide Teile werden in jeweils einen (mehrzeiligen) String eingeschlossen:&lt;br /&gt;
&lt;br /&gt;
  +--------+     +----+            setup = &amp;quot;&amp;quot;&amp;quot;            prog = &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
  |  algo  | --&amp;gt; |init|                +----+                 +----+&lt;br /&gt;
  |        |     +----+                |init|                 |prog|&lt;br /&gt;
  |        |                           +----+                 +----+&lt;br /&gt;
  |        |     +----+             &amp;quot;&amp;quot;&amp;quot;                     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
  |        | --&amp;gt; |prog|            &lt;br /&gt;
  +--------+     +----+            &lt;br /&gt;
&lt;br /&gt;
* aus den beiden Strings wird ein Timeit-Objekt erzeugt: &amp;lt;tt&amp;gt;t = timeit.Timer(prog, setup)&amp;lt;/tt&amp;gt;&lt;br /&gt;
* Frage: Wie oft soll die Algorithmik wiederholt werden&lt;br /&gt;
:z.B. N = 1000&lt;br /&gt;
* Zeit in Sekunden für N Durchläufe: &amp;lt;tt&amp;gt;K = t.timeit(N)&amp;lt;/tt&amp;gt;&lt;br /&gt;
:Zeit für 1 Durchlauf: K/N&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
3.Stunde am 16.04.2008&lt;br /&gt;
&lt;br /&gt;
==Sortierverfahren==&lt;br /&gt;
&lt;br /&gt;
=== 1.Motivation ===&lt;br /&gt;
'''Def:''' &lt;br /&gt;
Ein Sortierverfahren ist ein Algorithmus, der dazu dient, eine Liste von Elementen zu sortieren.&lt;br /&gt;
* Literatur, ride Sortierverfahren; Bubblesort 1956, Quicksort 1962. Librarysort 2004  &lt;br /&gt;
&lt;br /&gt;
'''Anwendungen'''&lt;br /&gt;
* Sortierte Daten sind häufig Vorbedingungen für Suchverfahren (Speziell für Algorithmen mit Log(N) Komplexität)&lt;br /&gt;
* Darstellung von Daten gemäß menschlicher Wahrnehmung &lt;br /&gt;
* Bemerkung: aus programmiertechnischer Anwendungssicht hat das Sortierproblem an Relevanz verloren da&lt;br /&gt;
** Festplatten / Hauptspeicher heute weniger limitierenden Charakter haben, so dass komplizierte, speicher-sparende Sortieralgorithmen nur noch in wenigen Fällen benötigt werden.&lt;br /&gt;
** gängige Programmiersprachen heute typunabhängige Algorithmen zur Verfügung stellen. Der Programmierer braucht sich deshalb in den meisten Fällen nicht mehr um die Implementierung von Sortieralgorithmen zu kümmern. In C/C++ sorgen dafür beispielsweise Methoden aus der [http://de.wikipedia.org/wiki/Standard_Template_Library STL]&lt;br /&gt;
&lt;br /&gt;
=== 2. Vorraussetzungen/ Spielregeln ===&lt;br /&gt;
- Mengentheoretische Anforderungen a Elemente&lt;br /&gt;
- Datenspeicherung Array / Liste&lt;br /&gt;
- Charakterisierung von Algorithmen &lt;br /&gt;
-- Komplexität (Speicher/ Laufzeit)&lt;br /&gt;
-- Stabilität&lt;br /&gt;
-- allg. Eigenschaften (Rekursivität/ Vergleich/ Methoden)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== 2.1 Mengentheoretische  Anforderungen====&lt;br /&gt;
Definition Totale Ordnung/ Total gordnete Menge:&lt;br /&gt;
Eine Totale Ordnung / Total geordnete Menge ist eine binäre Relation    &lt;br /&gt;
&amp;lt;math&amp;gt;R \subseteq M \times M&amp;lt;/math&amp;gt; über einer Menge &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt;, die transitiv, antisymmetrisch und total ist.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; sei  dargestellt als infix Notation &amp;lt;math&amp;gt;\le &amp;lt;/math&amp;gt; dann, falls M total geordnet, gilt &lt;br /&gt;
&amp;lt;math&amp;gt; \forall a,b,c \ \epsilon M &amp;lt;/math&amp;gt; &amp;lt;br/&amp;gt;&lt;br /&gt;
(1) &amp;lt;math&amp;gt;a \le b \bigwedge b \le a \Rightarrow a=b &amp;lt;/math&amp;gt; (antisymmetrisch)&amp;lt;br/&amp;gt;&lt;br /&gt;
(2) &amp;lt;math&amp;gt;a \le b \bigwedge b \le c \Rightarrow a \le c &amp;lt;/math&amp;gt; (transitiv)&amp;lt;br/&amp;gt;&lt;br /&gt;
(3) &amp;lt;math&amp;gt;a \le b \bigvee b \le a &amp;lt;/math&amp;gt; (total) &amp;lt;br/&amp;gt;&lt;br /&gt;
Bemerkung: aus (3) folgt &amp;lt;math&amp;gt; a \le a &amp;lt;/math&amp;gt; (reflexiv) &amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
''Hab in der Wiki eine gute Seite dazu gefunden [http://de.wikipedia.org/wiki/Ordnungsrelation'' Ordnungsrelation]&lt;br /&gt;
&lt;br /&gt;
==== 2.2 Datenspeicherung ====&lt;br /&gt;
a) Array ---&lt;br /&gt;
        +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
        |///|   |    |   |   |   |    |   |///|  Einfügen oder Löschen&lt;br /&gt;
        +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
       \________________  ____________________/&lt;br /&gt;
                        \/&lt;br /&gt;
                        N&lt;br /&gt;
b) Vekettete Liste  &lt;br /&gt;
                    +---+    +---+     +---+&lt;br /&gt;
                    |    |-&amp;gt; |    |-&amp;gt;  |    |-&amp;gt; Z &lt;br /&gt;
                    +---+    +---+     +---+&lt;br /&gt;
   &lt;br /&gt;
Datenelement zeigt auf das Nächste &lt;br /&gt;
Nachteil: lineares array (Adressierung bsp: 10 &amp;gt; 9)&lt;br /&gt;
&lt;br /&gt;
==== Charakterisierung der Effizienz von Algorithmen ====&lt;br /&gt;
  &lt;br /&gt;
:(a) Komplexität O(   1), O(n), etc. wird in Kapitel [[Effizienz]] erklärt.&lt;br /&gt;
:(b) Zählen der notwendigen Vergleiche&lt;br /&gt;
:(c) Messen der Laufzeit mit 'timeit' (auf identischen Daten)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Rekursive Beziehungen'''&lt;br /&gt;
zerlegt die ursprünglichen Probleme in kleinere Probleme und wendet den Algorithmus auf die kleineren Probleme an; daraufhin werden die Teilprobleme zur Lösung des Gesamtproblems verwendet. &lt;br /&gt;
d.h. Laufzeit (operativer Vergleich) für N Eingaben hängt von der Laufzeit der Eingaben für die Teilprobleme &lt;br /&gt;
&lt;br /&gt;
'''Aufwand'''&lt;br /&gt;
&lt;br /&gt;
(i) rekursives/ lineares Durchlaufen der Eingabedaten, Bearbeitung einzelner Elemente&lt;br /&gt;
&lt;br /&gt;
 C(N)= C(N-1)+ N ;  N&amp;gt;1, C(1)= 1             +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
     = C(N-2) +(N-1)+ N                      | 7  | 3 | 2 | 5 | 6 | 8 | 1 | 4 | 2 |  &lt;br /&gt;
     = C(N-3) + (N-2) + (N-1) + N            +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
     = ...                                         ________________________/&lt;br /&gt;
     = C(1) + 2+...+(N-1) +N                     /&lt;br /&gt;
                                               +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
        N(N+1)   N²                            | 1 | 3 | 2 | 5 | 6 | 8 | 7 | 4 | 2 |  &lt;br /&gt;
      = -----  ~ --                            +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
          2       2                   &lt;br /&gt;
            &lt;br /&gt;
                                                      &lt;br /&gt;
                                               &lt;br /&gt;
   &lt;br /&gt;
&lt;br /&gt;
(ii) rekursives halbieren der Menge der Eingabedaten&lt;br /&gt;
    &lt;br /&gt;
 C(N)= C(N/2)+1 ; N&amp;gt;1, C(1)=0&lt;br /&gt;
 Aus Gründen der Einfachheit sei N  = 2n&lt;br /&gt;
 &lt;br /&gt;
 C(N)= C(2^n)= C(&amp;lt;math&amp;gt;2^{n-1}&amp;lt;/math&amp;gt;) + 1       &lt;br /&gt;
                     &lt;br /&gt;
              = C(&amp;lt;math&amp;gt;2^{n-1}&amp;lt;/math&amp;gt;) + 1 + 1   &lt;br /&gt;
              = ...                    &lt;br /&gt;
                                       &lt;br /&gt;
              = C(&amp;lt;math&amp;gt;2^0&amp;lt;/math&amp;gt;) + n               &lt;br /&gt;
              = n                    &lt;br /&gt;
              = &amp;lt;math&amp;gt;log_2 N&amp;lt;/math&amp;gt;                 &lt;br /&gt;
 +---+---+---+---+-|-+---+---+---+---+ &lt;br /&gt;
 |   |    |   |   |   |   |    |   |   |&lt;br /&gt;
 +---+---+---+---+-|-+---+---+---+---+&lt;br /&gt;
 +---+---+---+---+&lt;br /&gt;
 |   |    |   |   |&lt;br /&gt;
 +---+---+---+---+&lt;br /&gt;
 +---+---+        +---+&lt;br /&gt;
 |   |    |  -&amp;gt;   |   |&lt;br /&gt;
 +---+---+        +---+&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
(iii) rekursives halbieren, lineare Bearbeitung, jedes Elements&lt;br /&gt;
  &lt;br /&gt;
 C(N)= 2C(N/2)+ N; N&amp;gt;1, C(1)= 0&lt;br /&gt;
 Sei N= &amp;lt;math&amp;gt;2^n&amp;lt;/math&amp;gt;&lt;br /&gt;
 C(N)= C(&amp;lt;math&amp;gt;2^n&amp;lt;/math&amp;gt;)= 2C (&amp;lt;math&amp;gt;2^{n-1}&amp;lt;/math&amp;gt;)+ &amp;lt;math&amp;gt;2^n&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;=&amp;gt; &amp;lt;math&amp;gt; \cfrac{C(2^n)}{2^n}&amp;lt;/math&amp;gt; = &amp;lt;math&amp;gt; \cfrac{2C(2^{n-1})}{2^{n-1}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 = &amp;lt;math&amp;gt; \cfrac{2C(2^{n-2})+2^{n-1}}{2^{n-1}}+1&amp;lt;/math&amp;gt;&lt;br /&gt;
 = &amp;lt;math&amp;gt; \cfrac{2C(2^{n-2})}{2^{n-2}}+1 +1&amp;lt;/math&amp;gt;&lt;br /&gt;
 =...&lt;br /&gt;
 = n&lt;br /&gt;
&amp;lt;=&amp;gt; C(&amp;lt;math&amp;gt;2^n&amp;lt;/math&amp;gt;)= &amp;lt;math&amp;gt;2^n&amp;lt;/math&amp;gt; * n&lt;br /&gt;
&amp;lt;=&amp;gt; C= N log&amp;lt;math&amp;gt;_2&amp;lt;/math&amp;gt;N&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''(b) Stabilität''' &lt;br /&gt;
Definition: stabiles Sortierverfahren&lt;br /&gt;
Ein Sortierverfahren heißt stabil falls die relative Reihenfolge gleicher Schlüssel M der Ausgangsdaten beibehält.&lt;br /&gt;
&lt;br /&gt;
Beispiel:&lt;br /&gt;
&lt;br /&gt;
 (3,7)               /               (1,8)&lt;br /&gt;
 (4,2)              /stabil          (2,2)&lt;br /&gt;
 (4,1)             /                 (3,7)&lt;br /&gt;
                                      (4,2)&lt;br /&gt;
 (2,2)             \                  ....&lt;br /&gt;
 (1,8)              \  nicht stabil   (4,1)&lt;br /&gt;
                      \               (4,2)&lt;br /&gt;
&lt;br /&gt;
==3. Sortierverfahren== &lt;br /&gt;
&lt;br /&gt;
 insertion sort  N² &lt;br /&gt;
 selection sort  N²&lt;br /&gt;
 Bubblesort N log N&lt;br /&gt;
 Quicksort  N log N&lt;br /&gt;
&lt;br /&gt;
'''3.1 Selection Sort'''&lt;br /&gt;
&lt;br /&gt;
a) Algorithmus&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 for i= 1 to N-1&lt;br /&gt;
    min &amp;lt;- 1;&lt;br /&gt;
    for j= i+1 to N&lt;br /&gt;
       if a[j]&amp;lt; a min  &lt;br /&gt;
       min &amp;lt;- j&lt;br /&gt;
    swap(a[min], a[i]) //Elemente links von i befinden sich an endgültiger Position&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;math&amp;gt; \downarrow&amp;lt;/math&amp;gt;&lt;br /&gt;
 +---+---+---+---+---+---+---+&lt;br /&gt;
 | S | O | R | T | I | N | G |&lt;br /&gt;
 +---+---+---+---+---+---+---+&lt;br /&gt;
       i      &lt;br /&gt;
 +---|---+---+---+---+---+---+&lt;br /&gt;
 | G | O | R | T | I | N | S |&lt;br /&gt;
 +---|---+---+---+---+---+---+&lt;br /&gt;
            i&lt;br /&gt;
 +---+---|---+---+---+---+---+&lt;br /&gt;
 | G | I | R | T | O | N | S |&lt;br /&gt;
 +---+---|---+---+---+---+---+ &lt;br /&gt;
                i&lt;br /&gt;
 +---+---+---|---+---+---+---+&lt;br /&gt;
 | G | I | N | T | O | R | S |&lt;br /&gt;
 +---+---+---|---+---+---+---+ &lt;br /&gt;
                    i&lt;br /&gt;
 +---+---+---+---+---+---+---+&lt;br /&gt;
 | G | I | N | O | T | R | S |&lt;br /&gt;
 +---+---+---+---+---+---+---+  &lt;br /&gt;
..&lt;br /&gt;
&lt;br /&gt;
'''b) Komplexität'''&lt;br /&gt;
 - Anzahl Vergleiche&lt;br /&gt;
 &lt;br /&gt;
 C(N)= C(N-1)+ (N-1)&lt;br /&gt;
     = C(N-2)+ (N-2)+ (N-1)&lt;br /&gt;
     ...&lt;br /&gt;
     = 1+2 + ...+ (N-2)+ (N-1)&lt;br /&gt;
     = &amp;lt;math&amp;gt;\cfrac{(N-1)N}{2}&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt;\approx \cfrac{(N^2)}{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
  - Anzahl Austauschoperationen&lt;br /&gt;
 &lt;br /&gt;
  C(N)= C(N-1)+1; C(1)= 0; n&amp;gt;1&lt;br /&gt;
      = N-1&lt;br /&gt;
&lt;br /&gt;
'''c) Stabilität'''&lt;br /&gt;
&lt;br /&gt;
 Im Allg. zur Prüfung a[j]&amp;lt;a[min] ist Selection Sort stabil&lt;br /&gt;
 falls &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; nicht.&lt;br /&gt;
&lt;br /&gt;
'''3.2 Insertion Sort'''&lt;br /&gt;
 - Teil der Übung&lt;br /&gt;
 - Erweiteung: Shell sort&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
4. Stunde, am 17.04.2008&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
(Fortsetzung der Stunde vom 16.04.2008)&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Mergesort ===&lt;br /&gt;
==== a) Algorithmus ====&lt;br /&gt;
&lt;br /&gt;
Zugrunde liegende Idee:&lt;br /&gt;
* &amp;quot;Teile und herrsche&amp;quot;-Prinzip (divide and conquer) zur Zerlegung des Problems in Teilprobleme.&lt;br /&gt;
* Zusammenführen der Lösungen über Mischen (merging): &amp;quot;two-way&amp;quot; oder &amp;quot;multi-way&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
==== a.1) Algorithmus &amp;lt;tt&amp;gt;c ← merge(a,b)&amp;lt;/tt&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
 c = merge(a,b) ← {      # Kombination zweier sortierter Listen a[i] und b[j]&lt;br /&gt;
                         # zu einer sortierten Ausgabeliste c[k]&lt;br /&gt;
     a[M+1] ← maxint&lt;br /&gt;
     b[N+1] ← maxint&lt;br /&gt;
 &lt;br /&gt;
     for k ← 1 to M+N&lt;br /&gt;
         if a[i] &amp;lt; b[j]&lt;br /&gt;
             c[k] &amp;gt; a[i]&lt;br /&gt;
             i ← i+1&lt;br /&gt;
         else&lt;br /&gt;
             c[k] ← b[j]&lt;br /&gt;
             j ← j+1 &lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
==== a.2) '''rekursiver Mergesort''' ====&lt;br /&gt;
&lt;br /&gt;
  mergesort(m) ← {    #m ist ein Array&lt;br /&gt;
      if |m| &amp;gt; 1      #True, wenn m mehr als 1 Element hat.&lt;br /&gt;
          a ← mergesort(m[1:&amp;lt;|m|/2])&lt;br /&gt;
          b ← mergesort(m[(|m|/2)+1:|m|])&lt;br /&gt;
          c ← merge(a,b)&lt;br /&gt;
          return(c)&lt;br /&gt;
      else&lt;br /&gt;
          return(m)&lt;br /&gt;
 } &lt;br /&gt;
&lt;br /&gt;
(Eine In-place-Implementierung siehe bei Sedgewick.)&lt;br /&gt;
&lt;br /&gt;
Bei der Sortierung mit Mergesort wird das Array immer in zwei Teile geteilt. → Es entsteht ein Binärbaum der Tiefe &amp;lt;math&amp;gt;lgN&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Gegebene unsortierte Liste: [S,O,R,T,I,N,G]&lt;br /&gt;
Die Teile werden bei jedem Schritt paarweise gemischt.&lt;br /&gt;
&lt;br /&gt;
 Schritt 0:&lt;br /&gt;
  S 0 R T I N G     S    O R    T I    N     G    #Arraylänge: N/8&lt;br /&gt;
 Schritt 1:          \  /   \  /   \  /     /&lt;br /&gt;
  OS RT IN G          OS     RT     IN     /      #Arraylänge: N/4    Vergleiche: N/4 * 4&lt;br /&gt;
 Schritt 2:             \    /        \   /       &lt;br /&gt;
  ORST GIN               ORST          GIN        #Arraylänge: N/2    Vergleiche: N/2 * 2&lt;br /&gt;
                            \         /&lt;br /&gt;
 Schritt3:                   \       /&lt;br /&gt;
  GINORST                     GINORST             #Arraylänge: N      Vergleiche: N              &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Zeitkomplexität: &amp;lt;math&amp;gt;C(N) - N \cdot lgN&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== b) Komplexität ====&lt;br /&gt;
Komplexität: &amp;lt;math&amp;gt;C(N) = 2 \cdot C \left( \frac{N}{2} \right) + N = N \cdot log_2 N &amp;lt;/math&amp;gt;     (für N = &amp;lt;math&amp;gt;2^n&amp;lt;/math&amp;gt; )&lt;br /&gt;
&lt;br /&gt;
Erklärungen zur Formel: &lt;br /&gt;
* &amp;lt;math&amp;gt; C \left(\frac{N}{2}\right) &amp;lt;/math&amp;gt;: &amp;quot;für jede Hälfte des Arrays&amp;quot;&lt;br /&gt;
* &amp;lt;math&amp;gt; +N &amp;lt;/math&amp;gt;: für das Zusammenführen&lt;br /&gt;
* N Vergleiche pro Ebene&lt;br /&gt;
* Insgesamt gibt es &amp;lt;math&amp;gt; log_2 N &amp;lt;/math&amp;gt; Ebenen.&lt;br /&gt;
&lt;br /&gt;
==== c) Weitere Eigenschaften von MergeSort ====&lt;br /&gt;
&lt;br /&gt;
* Mergesort ist '''stabil''', weil die Position gleicher Schlüssel im Algorithmus &amp;lt;tt&amp;gt;merge(a,b)&amp;lt;/tt&amp;gt; nicht verändert wird - wegen &amp;lt;tt&amp;gt;„&amp;lt;”&amp;lt;/tt&amp;gt; hat das linke Element Vorrang.&lt;br /&gt;
* Mergesort ist '''unempfindlich gegenüber der ursprünglichen Reihenfolge der Eingabedaten'''. Grund dafür ist&lt;br /&gt;
** die vollständige Aufteilung des Ausgangsarrays in Arrays der Länge 1 und&lt;br /&gt;
** dass &amp;lt;tt&amp;gt;merge(a,b)&amp;lt;/tt&amp;gt; die Vorsortierung nicht ausnutzt, d.h. die Komplexität von &amp;lt;tt&amp;gt;merge(a,b)&amp;lt;/tt&amp;gt; ist sortierungsunabhängig.&lt;br /&gt;
* Diese Eigenschaft ist dann unerwünscht, wenn ein Teil des Arrays oder gar das ganze Array schon sortiert ist. Es wird nämlich in jedem Fall das ganze Array neu sortiert.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Quicksort ===&lt;br /&gt;
&lt;br /&gt;
* Quicksort wurde in den 60er Jahren von Charles Antony Richard Hoare [http://de.wikipedia.org/wiki/C._A._R._Hoare] entwickelt. Es gibt viele Implementierungen von Quicksort, vgl. [http://de.wikipedia.org/wiki/Quicksort].&lt;br /&gt;
* Dieser Algorithmus gehört zu den &amp;quot;Teile und herrsche&amp;quot;-Algorithmen (divide-and-conquer) und ist der Standardalgorithmus für Sortieren.&lt;br /&gt;
&lt;br /&gt;
==== a) Algorithmus für &amp;lt;tt&amp;gt;quicksort&amp;lt;/tt&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
  quicksort(l,r) ← {    #l: linke Grenze, r: rechte Grenze des Arrays&lt;br /&gt;
                        #Das Array läuft also von l bis r (a[l:r])&lt;br /&gt;
      if r &amp;gt; l&lt;br /&gt;
          i ← partition(l,r)    #i ist das Pivot-Element&lt;br /&gt;
          quicksort(l,i-1)      #quicksort auf beide Hälfte des Arrays anwenden&lt;br /&gt;
          quicksort(i+1,r)&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
Dieser Algorithmus wird rekursiv für zwei Teilstücke links und rechts des Pivot-Elements aufgerufen. Das Pivot-Element ist nach diesem Schritt an der richtigen Position (d.h. links von der Stelle &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt; stehen nur kleinere, rechts von &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt; nur größere Elemente als das Pivot-Element).&lt;br /&gt;
Die Methode &amp;lt;tt&amp;gt;partition&amp;lt;/tt&amp;gt; sorgt dafür, dass diese Bedingung erfüllt ist.&lt;br /&gt;
&lt;br /&gt;
==== b) Algorithmus für &amp;lt;tt&amp;gt;partition&amp;lt;/tt&amp;gt; ====&lt;br /&gt;
Aufgabe: Ordne &amp;lt;tt&amp;gt;a&amp;lt;/tt&amp;gt; so um, dass nach der Wahl von &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt; (Pivot-Element) gilt:&lt;br /&gt;
# &amp;lt;math&amp;gt;a[i]&amp;lt;/math&amp;gt; ist sortiert, d.h. dieses Element ist am endgültigen Platz.&lt;br /&gt;
# &amp;lt;math&amp;gt;\forall x \in \left\{ a \left[ l \right] , ... a \left[ i-1 \right] \right\} : x \leq a \left[ i \right]&amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt;\forall x \in \left\{ a \left[ i+1 \right], ... a \left[ r \right] \right\} : x \geq a \left[ i \right]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* a[i] heißt Pivot-Element (p)&lt;br /&gt;
&lt;br /&gt;
          l                               r&lt;br /&gt;
        +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 Array: |   |   |   |   |\\\|   |   |   |   |&lt;br /&gt;
        +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
         \______  _____/  i  \______  _____/ &lt;br /&gt;
                \/                  \/&lt;br /&gt;
             &amp;lt;=a[i]               &amp;gt;=a[i]             (a[i] ist das Pivot-Element)&lt;br /&gt;
&lt;br /&gt;
  i ← partition(l,r) ← {&lt;br /&gt;
      p ← a[r]              #p: Pivot-Element. Hier wird willkürlich das rechteste  &lt;br /&gt;
                            #   Element als Pivot-Element genommen.&lt;br /&gt;
      i ← l-1               #i und j sind Laufvariablen&lt;br /&gt;
      j ← r&lt;br /&gt;
      &lt;br /&gt;
      repeat&lt;br /&gt;
          repeat&lt;br /&gt;
              i ← i+1       #Finde von links den ersten Eintrag &amp;gt;= p&lt;br /&gt;
          until a[i] &amp;gt;= r&lt;br /&gt;
      &lt;br /&gt;
          repeat&lt;br /&gt;
              j ← j-1       #Finde von rechts den ersten Eintrag &amp;lt; p&lt;br /&gt;
          until a[j] &amp;lt;= r&lt;br /&gt;
          swap(a[i], a[j])&lt;br /&gt;
      until j &amp;lt;= i          #Nachteile: p steht noch rechts&lt;br /&gt;
      swap(a[i], a[j])      #Letzter Austausch zwischen i und j muss &lt;br /&gt;
                            #zurückgenommen werden&lt;br /&gt;
      swap(a[i], a[r])      #Das Pivot-Element wird an die korrekte Position gesetzt.&lt;br /&gt;
      return(i)&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
                          p&lt;br /&gt;
        +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 Array: |   |   |   |   |\\\|   |   |   |   |&lt;br /&gt;
        +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
        -------&amp;gt; i&amp;gt;p             j&amp;lt;p &amp;lt;-------&lt;br /&gt;
                  |               |&lt;br /&gt;
                  +---------------+&lt;br /&gt;
       Diese zwei Elemente werden ausgetauscht.&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
                          p&lt;br /&gt;
        +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 Array: |   |   |   |   |\\\|   |   |   |   |&lt;br /&gt;
        +---+---+---+---+---+---+---+---+---+&lt;br /&gt;
        -----------------&amp;gt; &amp;lt;-----------------&lt;br /&gt;
  &lt;br /&gt;
 &amp;quot;repeat&amp;quot; bis sich die Zeiger treffen oder einander überholt haben.&lt;br /&gt;
&lt;br /&gt;
  l,i --&amp;gt;                                          &amp;lt;-- j   r&lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 | A | S | O | R | T | I | N | G | E | X | A | M | P | L | E |&lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 &lt;br /&gt;
       i                                   j               r&lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 | A | S | O | R | T | I | N | G | E | X | A | M | P | L | E |&lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 &lt;br /&gt;
           i                       j                       r&lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 | A | A | O | R | T | I | N | G | E | X | S | M | P | L | E |&lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 &lt;br /&gt;
           j   i                                           r&lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 | A | A | R | E | T | I | N | G | O | X | S | M | P | L | E |   --&amp;gt; Hier wird die &lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+       Schleife verlassen.&lt;br /&gt;
 &lt;br /&gt;
           j   i                                           r&lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 | A | A | E | R | T | I | N | G | O | X | S | M | P | L | E |   1.swap&lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 &lt;br /&gt;
               i                                           r&lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
 | A | A | E | E | T | I | N | G | O | X | S | M | P | L | R |   2.swap&lt;br /&gt;
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Bemerkungen zur gegebenen Implementierung:&lt;br /&gt;
#  Die gegebene Implementierung benötigt ein Dummy-Minimalelement (für den Fall &amp;lt;tt&amp;gt;i=1&amp;lt;/tt&amp;gt;).&lt;br /&gt;
#* Dieses Element ist durch zusätzliche &amp;lt;tt&amp;gt;if&amp;lt;/tt&amp;gt;-Abfrage vermeidbar, aber die &amp;lt;tt&amp;gt;if&amp;lt;/tt&amp;gt;-Abfrage erhöht die Komplexität des Algorithmus (schlechte Performanz).&lt;br /&gt;
# Sie ist uneffizient für (weitgehend) sortierte Arrays, da sich folgende Aufteilung für die Partitionen ergibt:&lt;br /&gt;
#* Erste Partition: &amp;lt;tt&amp;gt;[l,i-1]&amp;lt;/tt&amp;gt;, zweite Partition: &amp;lt;tt&amp;gt;[i+1,r]&amp;lt;/tt&amp;gt;&lt;br /&gt;
#* Die erste Partition umfasst &amp;lt;tt&amp;gt;N-1&amp;lt;/tt&amp;gt; Elemente. Die zweite Partition ist leer (bzw. sie existiert nicht), weil das Pivot-Element &amp;lt;tt&amp;gt;p = r&amp;lt;/tt&amp;gt; gewählt wurde. &lt;br /&gt;
#* Das Array wird elementweise abgearbeitet. → &amp;lt;tt&amp;gt;N&amp;lt;/tt&amp;gt; einzelne Aufrufe ⇒ Zeitkomplexität: &amp;lt;math&amp;gt;\approx\frac{N^2}{2}&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Für identische Schlüssel sollten beide Laufvariablen stehen bleiben (daher &amp;quot;&amp;lt;tt&amp;gt;&amp;lt;math&amp;gt;\leq&amp;lt;/math&amp;gt;&amp;lt;/tt&amp;gt;&amp;quot;), um ausgeglichene Zerlegungen bei vielen identischen Schlüsseln zu gewährleisten.&lt;br /&gt;
# Bei der gegebenen Implementierung tauscht auch gleiche Elemente aus.&lt;br /&gt;
# Für identische Schlüssel können die Abbruchbedingungen verbessert werden (siehe Sedgewick, Seite 150).&lt;br /&gt;
&lt;br /&gt;
==== c) Komplexität ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C(N) = (N+1) + \frac{1}{N} \sum_{k=1}^{N} \left[ C(k-1) + C(N-k) \right]&amp;lt;/math&amp;gt; für &amp;lt;math&amp;gt; N&amp;gt;1;\, C_1 = C_0 =0 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Anmerkungen zur Formel:&lt;br /&gt;
# &amp;lt;math&amp;gt;(N+1)&amp;lt;/math&amp;gt;: Vergleiche für jeden Aufruf&lt;br /&gt;
# &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt;: Teilungspunkt&lt;br /&gt;
# &amp;lt;math&amp;gt; \frac{1}{N} \sum_{k=1}^{N} \left[ C(k-1) + C(N-k) \right] = 2 \frac{1}{N} \sum_{k=1}^{N} C(k-1) &amp;lt;/math&amp;gt; ist der mittlere Aufwand über alle möglichen Zerlegungen.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
C(N) = (N+1) + \frac{2}{N} \sum_{k=1}^{N} C(k-1) &amp;lt;/math&amp;gt; &amp;lt;math&amp;gt;\overset{\cdot N}{\longleftrightarrow} &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
N \cdot C(N) = N \left[ (N+1) + \frac{2}{N} \sum_{k=1}^{N} C(k-1) \right] \overset{-\, (N-1) \cdot C(N-1)}{\longleftrightarrow} &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
N \cdot C(N) - (N-1) \cdot C(N-1) = N(N+1) - (N-1) N + 2 \sum_{k=1}^{N} C(k-1) - 2 \sum_{k=1}^{N} C(k-1) &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
= N(N+1) - (N-1) N + 2 \cdot C(N-1) \longleftrightarrow &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
N \cdot C(N) = N(N+1) - (N-1) N + 2 \cdot C(N-1) + (N-1) \cdot &lt;br /&gt;
&lt;br /&gt;
C(N-1) = 2N + (N+1) \cdot C(N-1) \overset{/N(N+1)}{\longleftrightarrow} &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{C(N)}{N+1} = \frac{C(N-1)}{N} + \frac{2}{N+1} &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; &lt;br /&gt;
= \frac{C(N-2)}{N-1} + \frac{2}{N} + \frac{2}{N+1} &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
= \frac{C(N-3)}{N-2} + \frac{2}{N-1} + \frac{2}{N} + \frac{2}{N+1} &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
= ... = &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
= \frac{C(2)}{3} + 2 \sum_{k=3}^{N} \frac{1}{k+1} \approx 2 \sum_{k=3}^{N} \frac{1}{k+1} \approx 2 \int_1^N \frac{1}{k} dk = 2 \cdot ln N &lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
Für sehr große N gilt:&lt;br /&gt;
&amp;lt;math&amp;gt;\approx 2 \sum_{k=1}^{N} \frac{1}{k}&amp;lt;/math&amp;gt; beziehungsweise &amp;lt;math&amp;gt; \geq 2 &lt;br /&gt;
&lt;br /&gt;
\sum_{k=1}^{N} \frac{1}{k}&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
Mittlere Komplexität:&lt;br /&gt;
&amp;lt;math&amp;gt;C(N) = 2(N+1) \cdot lnN \approx 2N \cdot lnN &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== d) Verbesserungen des Quicksort-Algorithmus ====&lt;br /&gt;
&lt;br /&gt;
===== Beseitigung der Rekursion =====&lt;br /&gt;
* Eine Verbesserung beseitigt die Rekursion durch Verwendung eines Stacks. Nach jeder Partitionierung wird das größere Teilintervall auf dem Stack abgelegt und das kleinere Teilintervall direkt weiterverarbeitet.&lt;br /&gt;
&lt;br /&gt;
 quickSort(l,r) ← {&lt;br /&gt;
     #initialize stack&lt;br /&gt;
     push([l,r])&lt;br /&gt;
     repeat&lt;br /&gt;
         if r &amp;gt; l:&lt;br /&gt;
             i ← partition(l,r)&lt;br /&gt;
             if (i-l) &amp;gt; (r-i):&lt;br /&gt;
                 push([l,i-1])&lt;br /&gt;
                 l ← i+1&lt;br /&gt;
             else:&lt;br /&gt;
                 push([i+1,r])&lt;br /&gt;
                 r ← i-1&lt;br /&gt;
         else:&lt;br /&gt;
             [l,r] ← pop()&lt;br /&gt;
     until stackempty()&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
 +---+---+---+---+---+---+---+&lt;br /&gt;
 | Q | U | I | C | K | S | O |&lt;br /&gt;
 +---+---+---+---+---+---+---+&lt;br /&gt;
 &lt;br /&gt;
              &lt;br /&gt;
 +---+---+---+===+---+---+---+&lt;br /&gt;
 | K | C | I |=O=| Q | S | U |&lt;br /&gt;
 +---+---+---+===+---+---+---+&lt;br /&gt;
                  \_________/&lt;br /&gt;
                      push&lt;br /&gt;
 &lt;br /&gt;
 +---+===+---+&lt;br /&gt;
 | C |=I=| K |&lt;br /&gt;
 +---+===+---+&lt;br /&gt;
          \_/&lt;br /&gt;
          push&lt;br /&gt;
 &lt;br /&gt;
 +===+&lt;br /&gt;
 |=C=|&lt;br /&gt;
 +===+&lt;br /&gt;
 &lt;br /&gt;
         +===+&lt;br /&gt;
         |=K=|&lt;br /&gt;
         +===+&lt;br /&gt;
 &lt;br /&gt;
                 +---+---+===+&lt;br /&gt;
                 | Q | S |=U=|&lt;br /&gt;
                 +---+---+===+&lt;br /&gt;
 &lt;br /&gt;
                 +---+===+&lt;br /&gt;
                 | Q |=S=|&lt;br /&gt;
                 +---+===+&lt;br /&gt;
 &lt;br /&gt;
                 +===+&lt;br /&gt;
                 |=Q=|&lt;br /&gt;
                 +===+&lt;br /&gt;
         &lt;br /&gt;
 +---+---+---+---+---+---+---+&lt;br /&gt;
 | C | I | K | O | Q | S | U |  &lt;br /&gt;
 +---+---+---+---+---+---+---+&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===== Alternatives Sortieren kleiner Intervalle =====&lt;br /&gt;
* Für kleine Intervalle ist Insertion Sort effizienter als &amp;quot;Teile und herrsche&amp;quot;&lt;br /&gt;
* Modifikation:&lt;br /&gt;
 if (r-l) &amp;lt;= K:&lt;br /&gt;
     insertion(l,r)&lt;br /&gt;
 else:&lt;br /&gt;
     #wie bisher&lt;br /&gt;
* für &amp;lt;tt&amp;gt;M = 3&amp;lt;/tt&amp;gt;: explizites Sortieren von 3 Elementen. - Sortieren von 3 Datensätzen. Idee:&lt;br /&gt;
# Stelle sicher, dass &amp;lt;tt&amp;gt;a[1]&amp;lt;/tt&amp;gt; und &amp;lt;tt&amp;gt;a[2]&amp;lt;/tt&amp;gt; relativ zueinander sortiert sind.&lt;br /&gt;
# Falls &amp;lt;tt&amp;gt;a[1]&amp;lt;/tt&amp;gt; jetzt noch größer als &amp;lt;tt&amp;gt;a[3]&amp;lt;/tt&amp;gt; ist, so ist &amp;lt;tt&amp;gt;a[3]&amp;lt;/tt&amp;gt; das kleinste Element und muss nach vorne geschrieben werden. Das heißt: &amp;lt;tt&amp;gt;swap(a[i], a[3])&amp;lt;/tt&amp;gt;. Danach steht das kleinste Element in &amp;lt;tt&amp;gt;a[1]&amp;lt;/tt&amp;gt;.&lt;br /&gt;
# Jetzt kann es entweder sein, dass &lt;br /&gt;
#* in 2. kein swap durchgeführt wurde und &amp;lt;tt&amp;gt;a[3]&amp;lt;/tt&amp;gt; eventuell zwischen &amp;lt;tt&amp;gt;a[1]&amp;lt;/tt&amp;gt; und &amp;lt;tt&amp;gt;a[2]&amp;lt;/tt&amp;gt; liegen könnte, oder&lt;br /&gt;
#* der &amp;lt;tt&amp;gt;swap&amp;lt;/tt&amp;gt; durchgeführt wurde und die Reihenfolge von &amp;lt;tt&amp;gt;a[2]&amp;lt;/tt&amp;gt; und &amp;lt;tt&amp;gt;a[3]&amp;lt;/tt&amp;gt; jetzt falsch ist.&lt;br /&gt;
# Falls &amp;lt;tt&amp;gt;a[2] &amp;gt; a[3]&amp;lt;/tt&amp;gt;, dann &amp;lt;tt&amp;gt;swap(a[2], a[3])&amp;lt;/tt&amp;gt;. Das löst beide in 3. genannten Probleme.&lt;br /&gt;
&lt;br /&gt;
* Der Algorithmus zum Sortieren von drei Datensätzen:&lt;br /&gt;
 a = sort2(a) ← {&lt;br /&gt;
     if a[1] &amp;gt; a[2]:&lt;br /&gt;
         swap(a[1], a[2])&lt;br /&gt;
     if a[1] &amp;gt; a[3]:&lt;br /&gt;
         swap(a[1], a[3])&lt;br /&gt;
                            # Man könnte hier &lt;br /&gt;
                            #     swap(a[2],a[3])&lt;br /&gt;
                            #     return&lt;br /&gt;
                            # einfügen und eine if-Abfrage sparen.&lt;br /&gt;
     if a[2] &amp;gt; a[3]:&lt;br /&gt;
         swap(a[2], a[3])&lt;br /&gt;
 } &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===== Günstige Selektion des Pivot-Elements =====&lt;br /&gt;
* Das Pivot-Element könnte geschickter gewählt werden. Methode: Median von drei Elementen: Bestimme den Median der ersten, mittleren und letzten Elements eines Arrays und verwende der Median als Pivot-Element&lt;br /&gt;
* Diese Methode minimiert die Häufigkeit des Auftetens des ungünstigsten Falles.&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
[[Special:Contributions/147.142.207.188|147.142.207.188]] 19:31, 23 April 2008 (UTC)&lt;/div&gt;</summary>
		<author><name>Jschleic</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Generizit%C3%A4t&amp;diff=1756</id>
		<title>Generizität</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Generizit%C3%A4t&amp;diff=1756"/>
				<updated>2008-06-17T08:56:57Z</updated>
		
		<summary type="html">&lt;p&gt;Jschleic: Gliederung des Artikels&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Ziel von generischer Programmierung ist es, Algorithmen und Datenstrukturen so zu entwerfen und zu implementieren, dass sie möglichst vielvältig verwendbar sind.&lt;br /&gt;
&lt;br /&gt;
'''Gemeint sind :'''&lt;br /&gt;
&lt;br /&gt;
*verschiedene Anwendungen                         &lt;br /&gt;
*mit vielen Kombinationsmöglichkeiten&lt;br /&gt;
*als wiederverwendbare Bibliothek&lt;br /&gt;
&lt;br /&gt;
--&amp;gt; ''' ohne Neuimplemenation '''&lt;br /&gt;
*Code austauschen in Bibliotheken&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Beispiel : ===&lt;br /&gt;
Kopieren eines Containers&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 def copyArray(a):&lt;br /&gt;
   r =[]&lt;br /&gt;
   for k in a&lt;br /&gt;
   r.append(k)&lt;br /&gt;
 return k&lt;br /&gt;
&lt;br /&gt;
 class Node :&lt;br /&gt;
  def__init__(self,data,next)&lt;br /&gt;
     self.data = data&lt;br /&gt;
     self.next = next&lt;br /&gt;
  return k&lt;br /&gt;
&lt;br /&gt;
 def copyArrayToList(a) :&lt;br /&gt;
    if len(a) == 0 : return None&lt;br /&gt;
       first = last = Node (a[0], None)&lt;br /&gt;
    for k in a[1:]  :&lt;br /&gt;
       last.next = Node(k, None)&lt;br /&gt;
       last = last.next&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 def copyListToArray(l):&lt;br /&gt;
     r = []&lt;br /&gt;
     while l is not in None :&lt;br /&gt;
         r.append(l.data)&lt;br /&gt;
         l = l.next&lt;br /&gt;
     return r&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== '''Beobachtung''' : ====&lt;br /&gt;
&lt;br /&gt;
Für '''N Datenstrukuren''' ist der Implementaionsaufwand &amp;lt;math&amp;gt;O({N^2})&amp;lt;/math&amp;gt;.&lt;br /&gt;
Alle Funktionen machen das gleiche mit uninteressanten Unterschied&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Verbesserung durch Verallgemeinerung zweier Aspekte''' :&lt;br /&gt;
&lt;br /&gt;
*Navigation durch die Quelldaten&lt;br /&gt;
*Aufbauen der Zieldatenstruktur&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Vereinheitlichung der Zieldatenstruktur :'''&lt;br /&gt;
*standardisierte Funktion &amp;quot;append&amp;quot;&lt;br /&gt;
*Array hat sie schon&lt;br /&gt;
*Liste : definiere Klasse DoublyLinkedList&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 class SentinelTag : pass     # keine Daten&lt;br /&gt;
 &lt;br /&gt;
 class DoublyLinkedNode:&lt;br /&gt;
  def__init__(self,data = sentinelTag(), next = None)&lt;br /&gt;
       self.data =data&lt;br /&gt;
       if next is None:&lt;br /&gt;
             self.prev = self.next = self&lt;br /&gt;
       else:&lt;br /&gt;
             self.next = next&lt;br /&gt;
             self.prev = next.prev&lt;br /&gt;
             next.prev.next = self&lt;br /&gt;
             next.prev = self&lt;br /&gt;
&lt;br /&gt;
 def isSentinel(self) : return isinstance(self.data, SentinelTag)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 class DoublyLinkedList :  # Realisiert doppelt verbundene kreisförmige Kette mit Seninel als Anker&lt;br /&gt;
   &lt;br /&gt;
       def__init__(self):&lt;br /&gt;
            self.sentinel = DoublyLinkedNode()&lt;br /&gt;
            self.size = 0&lt;br /&gt;
&lt;br /&gt;
       def__len__(self): return self.size   #len(l)&lt;br /&gt;
&lt;br /&gt;
       def append(self, value):&lt;br /&gt;
           DoublyLinkedNode(value, self.sentinel)&lt;br /&gt;
           self.size += size&lt;br /&gt;
  &lt;br /&gt;
       def__iter__(self):&lt;br /&gt;
            return ListIterator(self.sentinel.next)&lt;br /&gt;
       &lt;br /&gt;
       def reverseIterator(self):&lt;br /&gt;
             return ListIterator(self.sentinel.prev)&lt;br /&gt;
&lt;br /&gt;
===Iteratoren===&lt;br /&gt;
Navigation in der Quelldatenstruktur(&amp;lt;u&amp;gt;Iteratoren&amp;lt;/u&amp;gt;) soll&amp;lt;br/&amp;gt; für alle Datenstrukturen funktionieren&lt;br /&gt;
&lt;br /&gt;
*Objekt, das auf ein Element des Containers zeigt&lt;br /&gt;
*Zum nächsten Element weiter rücken kann&lt;br /&gt;
*anzeigt, wenn das Ende der Sequenz erreicht ist &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 def genericCopy(quelle, ziele) :&lt;br /&gt;
       for k in quelle :&lt;br /&gt;
             ziel.append(k)&lt;br /&gt;
   return ziel&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
 liste = genericCopy(array, DoublyLinkedList())       # Statt copyArrayToList&lt;br /&gt;
 array2 = genericCopy(array,[])                       # Statt copyArray &lt;br /&gt;
 array3 = genericCopy(liste,[])                       # Statt copyListToArray &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 class ListIterator:&lt;br /&gt;
    def__init__(self, node):&lt;br /&gt;
        self.node = node&lt;br /&gt;
    def next(self):&lt;br /&gt;
        if self.node.isSentinel():&lt;br /&gt;
           raise StopIteraion()       #Python Konvention&lt;br /&gt;
        v = self.node.data&lt;br /&gt;
        self.node = self.node.next    # zeigt Ende der Sequenz&lt;br /&gt;
        return v                      # Pythonkonvention, gebe vorigen Wert zurück&lt;br /&gt;
&lt;br /&gt;
    def__iter__(self):&lt;br /&gt;
       return ListIterator(self.node) # Pythonkonvention, Kopie des Iterators erzeugen&lt;br /&gt;
&lt;br /&gt;
'''besser stattdessen''' :&lt;br /&gt;
&lt;br /&gt;
      self__class__(self.node)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Was tut Python bei''' &amp;quot; for k in quelle&amp;quot;:&lt;br /&gt;
 &lt;br /&gt;
 iter = quelle__iter__()&lt;br /&gt;
 try :&lt;br /&gt;
        while True :&lt;br /&gt;
              k = iter.next()&lt;br /&gt;
              ...           # Schleifeninhalt&lt;br /&gt;
 except StopIteration: pass&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Rückwärts kopieren :'''&lt;br /&gt;
&lt;br /&gt;
 class ReverseListIterator(ListIterator)&lt;br /&gt;
   def next(self):&lt;br /&gt;
         if self.node isSentinel(): raise StopIteration()&lt;br /&gt;
       v = self.node.data&lt;br /&gt;
       self.node = self.node.prev&lt;br /&gt;
       return v&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 revArray = genericCopy(list, reverseIter(), []),&lt;br /&gt;
 revList = genericCopy(reversed(array), DoublyLinkedList())&lt;br /&gt;
&lt;br /&gt;
===Funktoren===&lt;br /&gt;
'''Verallgemeinerung auf Funktionen die &amp;quot; etwas tun&amp;quot;:'''&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;code&amp;gt;def sumArray(a):&lt;br /&gt;
         s = 0&lt;br /&gt;
         for k in a :&lt;br /&gt;
             s+=a       # s = add(s,k)&lt;br /&gt;
       return s &amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 def maxList(l):&lt;br /&gt;
    m = -1111111111111111&lt;br /&gt;
    while not l isSentinel:&lt;br /&gt;
         m = max(m, l.data)                    # max ist eingebaute Funkion in Python&lt;br /&gt;
         l =l.next&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Zur Verallgemeinerung werden Funktoren eingerichtet:'''&lt;br /&gt;
&lt;br /&gt;
*Funktor muss &amp;quot;callable&amp;quot; sein : falls f Funktor ist, funktioniert v = f(a1, a2,...) &lt;br /&gt;
*Funktion, oder Objekt bei dem die Funktion __call___ definiert ist.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 def doSomethingGeneric(functor,iterator, intial):&lt;br /&gt;
       for k in iterator&lt;br /&gt;
             initial = functor(initial, k)&lt;br /&gt;
    return initial&lt;br /&gt;
&lt;br /&gt;
'''Statt maxList:'''&lt;br /&gt;
&lt;br /&gt;
 m = doSomethingGeneric(max,list -11111111111)  &lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
'''Statt sumArray :'''&lt;br /&gt;
&lt;br /&gt;
 def add(x,y): return x + y&lt;br /&gt;
   s = doSomethingGeneric(add, array,o)&lt;br /&gt;
&lt;br /&gt;
'''Statt genericCopy :'''&lt;br /&gt;
&lt;br /&gt;
 def append(x,y):&lt;br /&gt;
      x.append(y)&lt;br /&gt;
    return x&lt;br /&gt;
&lt;br /&gt;
 array4 = doSomethingGeneric(append, array[])&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
'''doSomethingGeneric'''() gibt es in vielen Programmiersprachen&lt;br /&gt;
&lt;br /&gt;
    *in Python : reduce     &lt;br /&gt;
    *in C++ : accumulate&lt;br /&gt;
     ...funktionale Sprachen (Lisp, Haskell...)&lt;br /&gt;
&lt;br /&gt;
'''verwandte generische Funktionen'''&lt;br /&gt;
&lt;br /&gt;
 map: &lt;br /&gt;
&lt;br /&gt;
[x1, x2,...] --&amp;gt; [f(x1),f(x2),...]          # Funktor mit einem Argument &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Offered Interface versus Required Interface===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Interface:'''&lt;br /&gt;
*standardisierte Schnittstelle zwischen Algorithmen und Datenstruktur&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
====Offered Interface:====&lt;br /&gt;
&lt;br /&gt;
*Funktionalität, die eine Datenstruktur anbietet.&lt;br /&gt;
*Die Datensruktur sollte möglichst vielseitig sein.&lt;br /&gt;
&lt;br /&gt;
'''z.B. PythonList:''' &lt;br /&gt;
&lt;br /&gt;
* dynamisches Array&lt;br /&gt;
* stack&lt;br /&gt;
* deque&lt;br /&gt;
* linkedList&lt;br /&gt;
&lt;br /&gt;
*standardisiert durch abstrakte Datentypen&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
====Required Interface:====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
*Funktionalität, die Algorihmus benutzt&lt;br /&gt;
*das '''required Interface''' ist meist weniger als '''das offered Interface'''&lt;br /&gt;
&lt;br /&gt;
z.B.:&lt;br /&gt;
&lt;br /&gt;
'''RI''' lesender Zugriff&lt;br /&gt;
'''OI''' lesender Zugriff   schreibender Zugriff  Konstruktor remove...&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* standardisiert über Konzepte&lt;br /&gt;
* ADT sind Sammlungen zusammengehörender Konzepte&lt;br /&gt;
* RI sollten &amp;lt;u&amp;gt;minimal&amp;lt;/u&amp;gt; sein&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
====Konzepte ( + Hierarchie)====&lt;br /&gt;
&lt;br /&gt;
* copy Constructible ( P: copy.deepcopy)&lt;br /&gt;
* Default Constructible (v1 = v.__class__() ist aufrufbar ) # DoublylinkedNode&lt;br /&gt;
* EqualityComparable('=='), LessThanComparable('&amp;lt;')&lt;br /&gt;
* ThreeWayComparable(__cmp__ ist aufrufbar)&lt;br /&gt;
* Indexable(&amp;quot;a[k]&amp;quot;, k ist Integer)&lt;br /&gt;
* Mapping(&amp;quot;a[key]&amp;quot;, key ist arbitrary)&lt;br /&gt;
* Hashable(__hash__ für key)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Iteratoren : Forward(next), BidirektionalIterator(next, prev), RandomAccessIterator(nex(k))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Container : Sequence                                    # Array&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
====Mathematische Konzepte :====&lt;br /&gt;
&lt;br /&gt;
 Addable(__add__)&lt;br /&gt;
 Subtractable(__sub__)&lt;br /&gt;
 Multiplyable(__mul__)&lt;br /&gt;
 Dividable(__div__)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Ein offered Interface is mehr als ein required Interface.'''&lt;/div&gt;</summary>
		<author><name>Jschleic</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Priorit%C3%A4tswarteschlangen&amp;diff=1414</id>
		<title>Prioritätswarteschlangen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Priorit%C3%A4tswarteschlangen&amp;diff=1414"/>
				<updated>2008-05-28T10:44:11Z</updated>
		
		<summary type="html">&lt;p&gt;Jschleic: /* Heap */  Größe der Grafik geändert&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Prioritätswarteschlangen==&lt;br /&gt;
===Heap===&lt;br /&gt;
*Datenstruktur optimiert für Prioritätssuche&lt;br /&gt;
*Def: ein linkslastiger Binärbaum ist ein Baum mit &amp;lt;math&amp;gt;d(node.left) \geq d(node.right)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ein Heap ist ein linkslastiger, perfekt balancierter Baum.&lt;br /&gt;
&lt;br /&gt;
Man kann einen Heap leicht als Array implementieren, wie folgende Grafik veranschaulicht:&lt;br /&gt;
[[Image:heapArray.png|left|400px]]&lt;/div&gt;</summary>
		<author><name>Jschleic</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Priorit%C3%A4tswarteschlangen&amp;diff=1413</id>
		<title>Prioritätswarteschlangen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Priorit%C3%A4tswarteschlangen&amp;diff=1413"/>
				<updated>2008-05-28T10:40:02Z</updated>
		
		<summary type="html">&lt;p&gt;Jschleic: heap - first edit&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Prioritätswarteschlangen==&lt;br /&gt;
===Heap===&lt;br /&gt;
*Datenstruktur optimiert für Prioritätssuche&lt;br /&gt;
*Def: ein linkslastiger Binärbaum ist ein Baum mit &amp;lt;math&amp;gt;d(node.left) \geq d(node.right)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ein Heap ist ein linkslastiger, perfekt balancierter Baum.&lt;br /&gt;
&lt;br /&gt;
Man kann einen Heap leicht als Array implementieren, wie folgende Grafik veranschaulicht:&lt;br /&gt;
[[Image:heapArray.png]]&lt;/div&gt;</summary>
		<author><name>Jschleic</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=File:HeapArray.png&amp;diff=1412</id>
		<title>File:HeapArray.png</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=File:HeapArray.png&amp;diff=1412"/>
				<updated>2008-05-28T10:34:42Z</updated>
		
		<summary type="html">&lt;p&gt;Jschleic: Man kann einen Heap-Baum als Array abspeichern, indem man die Elemente wie gezeigt durchnummeriert.&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Man kann einen Heap-Baum als Array abspeichern, indem man die Elemente wie gezeigt durchnummeriert.&lt;/div&gt;</summary>
		<author><name>Jschleic</name></author>	</entry>

	</feed>