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

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1574</id>
		<title>Suchen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1574"/>
				<updated>2008-06-02T21:35:46Z</updated>
		
		<summary type="html">&lt;p&gt;Friedrich: /* Rotation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Es wäre super wenn jemand ganz kurz schreiben könnte, was am Donnerstag zu den Funktionen treeInsert/Remove/HasKey gemacht wurde, was man ja für den aktuellen Übungszettel braucht. Danke :-)&amp;lt;br /&amp;gt;&lt;br /&gt;
/edit: an eine Funktion &amp;quot;HasKey&amp;quot; kann ich mich aus der Vorlesung nicht erinnern. Wenn die jemand hat, bitte eintragen. - Protokollant&lt;br /&gt;
&lt;br /&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;
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 einer oder mehrerer Datensätze aus einer Menge von früher gespeicherten Datensätzen. Ein paar einleitende Worte zum Suchproblem findet man [http://de.wikipedia.org/wiki/Suche hier].&lt;br /&gt;
&lt;br /&gt;
== Überblick verschiedener Suchmethoden ==&lt;br /&gt;
&lt;br /&gt;
Um sich der Vielseitigkeit von Suchproblemen 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 wichtigster 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 (Pseudo)-Python-Code zeigt eine Implementation der Suchmethode.&lt;br /&gt;
&lt;br /&gt;
 a = ... # array&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;
 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 innerste Vergleich (a[i] == key) jeweils &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt; ist (diese Annahme könnte verletzt sein, wenn der Vergleichsoperator überladen ist und dadurch eine höhere Komplexität hat). Dieser Vergleich wird in der for-Schleife jeweils N-mal durchgeführt (&amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt;), so dass man nach der Verschachtelungsregel eine gesamte Komplexität von &amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt; erhält.&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, so ist unter Umständen die Implementierung über einen [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum Binärbaum] (siehe auch weiter unten) geschickter. &lt;br /&gt;
&lt;br /&gt;
Der folgende Algorithmus zeigt eine beispielhafte Implementierung der Methode (wir setzen wieder &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt;):&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;
 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   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    center = (start + end)/2   # Integer Division, 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  # &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&lt;br /&gt;
    else:&lt;br /&gt;
        return binSearch(a, key, start + 1, center)  # Rekursion&lt;br /&gt;
&lt;br /&gt;
Zur Berechnung der Komplexität dieses Algorithmus vernachlässigen wir zunächst den Aufwand, den die Sortierung weiter oben verursacht. Dieser Schritt mag oder mag nicht zulässig sein.&lt;br /&gt;
&lt;br /&gt;
Nach der Sequenzregel haben auch alle &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen 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 und ist &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;
Falls jetzt gilt &amp;lt;math&amp;gt; N = 2^n &amp;lt;/math&amp;gt;&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;
&lt;br /&gt;
== Suche in einem Binärbaum ==&lt;br /&gt;
&lt;br /&gt;
Eine kurze Einführung in Binärbäume findet man [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum hier].&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. Alle Schlüssel im linken Unterbaum sind kleiner, alle Schlüssel im rechten Unterbaum sind größer als ihr Vater. 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 in einem Baum suchen zu können, 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 binärer Suche wie oben 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;
Der folgende Python-Code zeigt beispielhaft, wie man in einem Suchbaum suchen könnte. Der Konstruktor für einen Knoten des Suchbaums ließe sich zum Beispiel so implementieren:&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;
 root = ...    # Wurzel des Suchbaums&lt;br /&gt;
 nodeFound = treeSearch(root, key)   # None, falls nichts gefunden&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:&lt;br /&gt;
         return node&lt;br /&gt;
     elif key &amp;lt; node.key:&lt;br /&gt;
         return treeSearch(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         return treeSearch(node.right, key)&lt;br /&gt;
&lt;br /&gt;
Daraus resultiert der folgende Suchalgorithmus:&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 None:&lt;br /&gt;
     treeSort(node.left, array)     # rekursiv&lt;br /&gt;
     array.append(node.key)     # &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;
 &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{leftleft})+f(N_\mathrm{leftright})+\mathcal{O}(1)+f(N_\mathrm{rightleft})&lt;br /&gt;
 +f(N_\mathrm{leftright})=N\ast\mathcal{O}(1)=\mathcal{O}(N)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sortier-Pseudocode:&lt;br /&gt;
&lt;br /&gt;
 Sortieren:&lt;br /&gt;
     (Array) a     # unsortiert&lt;br /&gt;
     (tree) t     # zunächst leer&lt;br /&gt;
 (dynamisches Array) r     # später sortiert&lt;br /&gt;
 for e in a:&lt;br /&gt;
     t = treeInsert(t, e)&lt;br /&gt;
 treeSort(t, r)&lt;br /&gt;
&lt;br /&gt;
== Insert ==&lt;br /&gt;
&lt;br /&gt;
Was passiert wenn der key (Schlüssel) schon vorhanden ist?&lt;br /&gt;
* error (exception)&lt;br /&gt;
* nichts einfügen&lt;br /&gt;
* nichts einfügen aber einen boolean return zurückgeben (einfügen=true, false)&lt;br /&gt;
* nochmals eingefügt (z.B. in der Node Klasse)&lt;br /&gt;
&lt;br /&gt;
Wobei die ersten 3 Punkte zur Mengensemantik gehören und der letzte eine Multimenge ist.&lt;br /&gt;
&lt;br /&gt;
Algorithmus im zweiten Fall (nichts einfügen):&lt;br /&gt;
&lt;br /&gt;
 def treeInsert(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return Node(key)     # Alternative Schreibweise: node = Node(key)&lt;br /&gt;
     if node.key == key:      # und dann:&lt;br /&gt;
         return node          # pass&lt;br /&gt;
     elif key &amp;lt; node.key:     # links im Baum&lt;br /&gt;
         node.left = treeInsert(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         node.right = treeInsert(node.right, key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Remove ==&lt;br /&gt;
Fälle:&lt;br /&gt;
# key (bzw. Knoten der key enthält) ist ein Blatt =&amp;gt; einfach löschen&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: max k &amp;lt; key (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; keys); Vorgänger ist immer ein Fall 1 oder Fall 2&lt;br /&gt;
#*:=&amp;gt; ersetze node durch Vorgänger und entferne Vorgänger&lt;br /&gt;
&lt;br /&gt;
 def treePredecessor(node):     # wird nur bei Fall 3 aufgerufen&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;
 def treeRemove(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return node&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:&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:&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;
* Pfad (Zwischen 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;): &lt;br /&gt;
**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;), sodass:&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 ein Graph, indem es zwischen beliebigen Knoten stets genau einen Pfad gibt.)&lt;br /&gt;
&lt;br /&gt;
* Länge eines Pfades: Anzahl der Kanten = Anzahl der Knoten - 1&lt;br /&gt;
* Tiefe eines Knotens: Pfadlänge vom Knoten zur Wurzel des Baumes.&lt;br /&gt;
* Tiefe des Baumes: maximale Tiefe eines Knotens&lt;br /&gt;
* Balance eines Baumes: maximale Tiefe(k) - minimale Tiefe(k) (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; Blätter)&lt;br /&gt;
&lt;br /&gt;
== Ungünstigster Fall von treeSearch ==&lt;br /&gt;
&lt;br /&gt;
Komplexität von treeSearch = Länge des Pfades zum Knoten wo &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; gefunden wird, oder erkannt wird, dass &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; nicht im Baum ist.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; wird nicht gefunden, aber für diese Entscheidung muss der Längste Pfad vollständig durchlaufen werden.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(T)&amp;lt;/math&amp;gt; wo T = Tiefe des Baumes&lt;br /&gt;
*: [1,2,3,4,5]:&amp;lt;br /&amp;gt; [[Image:Balance.png]]&lt;br /&gt;
*: Tiefe T = 4, Balance = 4&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; wo N = Anzahl der Elemente.&lt;br /&gt;
&lt;br /&gt;
== Aufgabe ==&lt;br /&gt;
&lt;br /&gt;
Minimiere Balance (erzeuge balancierten Baum):&lt;br /&gt;
# Einfügen in geschickter Reihenfolge (siehe Übungsaufgabe)&lt;br /&gt;
# Selbstbalancierter Baum:&lt;br /&gt;
#* Überprüfen der Balance nach jedem Einfügen&lt;br /&gt;
#* Umstrukturieren des Baumes, falls Balance &amp;gt; 1 (Suchbaum-Bedingung muss erhalten bleiben)&lt;br /&gt;
#* AVL-Bäume (älteste Variante)&lt;br /&gt;
#* Rot-Schwarz-Bäume (verbreiteste Variante)&lt;br /&gt;
#* Treaps (flexibelste Variante, siehe Übung)&lt;br /&gt;
#* Splay trees&lt;br /&gt;
#* Andersson Trees (einfachste Variante)&lt;br /&gt;
(#* Skip Lists (schnellste Variante, aber kein Binärbaum))&lt;br /&gt;
&lt;br /&gt;
== Umstrukturieren, so dass Suchbaumbedingung erhalten bleibt: ==&lt;br /&gt;
&lt;br /&gt;
Rotation: elementare Umstrukturierungen&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum_Rotation.png]]&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;
== Balance eines Suchbaumes ==&lt;br /&gt;
&lt;br /&gt;
===Balance eines Baumes zu  definieren:===&lt;br /&gt;
[[Image:Abbildung1.jpg|200px|right]]&lt;br /&gt;
*betrachte &amp;lt;tt&amp;gt;None&amp;lt;/tt&amp;gt; als zusätzlichen Knoten : '''sentinel'''   ([[englische Sprache|engl.]] für ''Wächter'')&lt;br /&gt;
*oder defeniere speziellen sentinel-Knoten&lt;br /&gt;
*RS - Pfades : von &amp;lt;tt&amp;gt;root&amp;lt;/tt&amp;gt; &amp;amp;rarr; ''sentinel''&lt;br /&gt;
*Länge eines Pfades ''P''      '''''|P|'''''&lt;br /&gt;
* &amp;lt;u&amp;gt;die Balance eines Baumes zu defenieren:&amp;lt;/u&amp;gt;&lt;br /&gt;
: '''''B = max P '''''&amp;lt;math&amp;gt; \in &amp;lt;/math&amp;gt; '''''{RS}    |P| - min |P|'''''&lt;br /&gt;
:'''''{RS}'''''  Menge aller RS-Pfade&lt;br /&gt;
;vollständiger Baum:  Balance &amp;lt;math&amp;gt;~=0&amp;lt;/math&amp;gt;&lt;br /&gt;
:Alle Knoten(außer Blättern) haben 2 Kinder.&lt;br /&gt;
;perfect balancierter Baum:  Balance  &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 1&lt;br /&gt;
'''alternative Defenition für perfect balancierte Bäume'''&lt;br /&gt;
:Für jeden Teilbaum gilt es: rechtes und linkes Kind (für jeden Knoten) sind auch wieder perfect balancierte Bäume und ihre Höhe unterscheidet sich höchstens um '''1'''.&lt;br /&gt;
&lt;br /&gt;
===Größe===&lt;br /&gt;
[[Image:baum-v.png|700px|right]]&lt;br /&gt;
====Größe des vollständigen Baumes====&lt;br /&gt;
Ebene K hat 2 &amp;lt;sup&amp;gt;k&amp;lt;/sup&amp;gt; Knoten.&lt;br /&gt;
Falls Tiefe = d , dann 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;
====Größe des perfect balancierten Baumes====&lt;br /&gt;
Die Tiefe d, kann nicht besser sein als vollständiger Baum&lt;br /&gt;
:'''=&amp;gt;'''N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2 &amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
;schlechteste perfect balancierter Baum : ist ein vollständiger der Tiefe  d - 1 + 1, N &amp;lt;math&amp;gt; \ge&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:'''=&amp;gt;'''  2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1)   &amp;lt;math&amp;gt;~&amp;lt;&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt;)&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
d &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt;  log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; (d + 1)&lt;br /&gt;
&lt;br /&gt;
*Ergibt die Komplexität der Suche im schlechtesten Fall: Anzahl der Vergleiche pro Knoten( = 2 bzw. = 3)&amp;lt;math&amp;gt;\ast&amp;lt;/math&amp;gt;Anzahl der Knoten&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow ~f(N)\le 2d \le 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
*'''perfekt balancierter Baum '''=&amp;gt; AVL-Baum &lt;br /&gt;
*'''balancierter Baum''':&lt;br /&gt;
::&amp;lt;math&amp;gt;\forall  N:d(N)\le c \ast d (N)&amp;lt;/math&amp;gt; und  &amp;lt;math&amp;gt;1 \le c &amp;lt; \mathcal {1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:: d ist die Tiefe von perfekt balancierten Baum&lt;br /&gt;
&lt;br /&gt;
:''Komplexität der Suche'':&amp;lt;math&amp;gt; ~f(N)\le  c\ast 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
:algorithmisch einfacher als perfekt balancierter Baum, aber fast genauso schnell&lt;br /&gt;
&lt;br /&gt;
====Selbst-balancierter Baum====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  def insertTree(node,key):&lt;br /&gt;
    if node is sentinel:       #(None = sentinel)&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 = insertTree(node.left, key)&lt;br /&gt;
    else:&lt;br /&gt;
        node.right = insertTree(node.right, key)&lt;br /&gt;
   #optimiere Balance:&lt;br /&gt;
        return node&lt;br /&gt;
&lt;br /&gt;
===Anderson-Bäume===&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 = selft.right = None&lt;br /&gt;
        #einfügen:&lt;br /&gt;
        self.level = 1&lt;br /&gt;
&lt;br /&gt;
:''level'' : kodiert Abstand von sentinel&lt;br /&gt;
====Regeln====&lt;br /&gt;
Es gibt vertikale Kanten(parent.level == child.level + 1 ) und horizontale Kanten(parent.level == child.level)&lt;br /&gt;
:*verfeinerter Regel :  ''level kodiert reduzierten Abstand von Setinel''  (d.b. horizontale Kanten werden nicht gezählt)&lt;br /&gt;
::(die nächste zwei Regeln sichern die Balance):&lt;br /&gt;
::*alle RS-Pfade haben die gleiche reduzierte Länge oder ''root'' hat bei allen Pfaden das gleiche Level&lt;br /&gt;
::*kein Pfad hat 2 aufeinanderer folgende horizontale Kanten&lt;br /&gt;
:*nur Kanten zum rechten Kind dürfen horizontal sein&lt;br /&gt;
:*die reduzierte Höhe jedes Blatts ist '''hr=1'''&lt;br /&gt;
&lt;br /&gt;
====Beweis====&lt;br /&gt;
Vereinfachung des Algorithmus&lt;br /&gt;
:'''Satz''':ein Anderson-Baum ist balanciert.&lt;br /&gt;
*1. Sei '''hr'''- die reduzierte Höhe&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; jeder Teilbaum enthält mindestens &amp;lt;math&amp;gt;N\ge 2^{hr} -1 &amp;lt;/math&amp;gt;  Knoten&lt;br /&gt;
&lt;br /&gt;
::a). Blätter : reduzierte Höhe 1 =&amp;gt; &amp;lt;math&amp;gt; N\ge 2^{1} - 1 = 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
::b). inneren Knoten: jeder Unterbaum hat mindestens reduzierte Höhe &amp;lt;math&amp;gt; ~hr - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt;  jeder Unterbaum hat mindestens &amp;lt;math&amp;gt; ~2^{hr} -1 &amp;lt;/math&amp;gt; Knoten&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt;N \ge 2 (2^{hr-1} -1)+1 = 2^w - 2 + 1 = 2^{hr} -1&amp;lt;/math&amp;gt;&lt;br /&gt;
::::alle RS-Pfade heben gleiche Länge&lt;br /&gt;
[[Image:Ander.jpg|450px|right]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
*2. Kein Pfad hat 2 aufeinanderfolgende horizontal Kanten&lt;br /&gt;
:::&amp;lt;math&amp;gt;\Rightarrow  d \le 2 hr + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
*3. zusammen:&lt;br /&gt;
:&amp;lt;math&amp;gt;N \ge 2^{\frac {d}{2}} - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;log_{2}{ N}+1\ge log 2^{\frac {d}{2}} - 1 \ge \frac {d}{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; ~d &amp;lt; 2 log_{2}{(N + 1)}&amp;lt;/math&amp;gt;     - balancierter Baum&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
:'''Suchzeit''' : &amp;lt;math&amp;gt; ~ f(N) = \mathcal{O}(log{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Wie erreicht man die Balance===&lt;br /&gt;
====Rotation====&lt;br /&gt;
[[Image:Abbildung4.jpg|text-top|500px|Illustration von Rotation]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
   def rotateRight (node):&lt;br /&gt;
      root = node.left&lt;br /&gt;
      node.left = root.right&lt;br /&gt;
      root.right = node&lt;br /&gt;
      return root&lt;br /&gt;
   def rotateLeft(node):&lt;br /&gt;
      root = node.right&lt;br /&gt;
      node.right = root.left&lt;br /&gt;
      root.left = node&lt;br /&gt;
      return root&lt;br /&gt;
&lt;br /&gt;
====Optimierung der Balance====&lt;br /&gt;
   &lt;br /&gt;
&lt;br /&gt;
       if node.left is not sentinel and node.level==node.left.level:&lt;br /&gt;
            node &amp;gt; rotateRight(node)&lt;br /&gt;
       if node.right is not sentinel and node.right.right is  not sentinel and node.level==rotate.right.right.level:&lt;br /&gt;
            node = rotateLeft(node)&lt;br /&gt;
            node.level += 1&lt;br /&gt;
[[Image:rotate.jpg|text-top]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
[[Image:liste.png]]&lt;/div&gt;</summary>
		<author><name>Friedrich</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Priorit%C3%A4tswarteschlangen&amp;diff=1573</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=1573"/>
				<updated>2008-06-02T20:33:57Z</updated>
		
		<summary type="html">&lt;p&gt;Friedrich: /* Heap */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Sortieren als Suchproblem==&lt;br /&gt;
Systematisches Fragen mit True und False kann auch als Baum dargestellt werden.&amp;lt;br&amp;gt; [[Image:penka.png|400px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Hier ein Beispiel.Als Eingabe sind drei Zahlen angegeben a={1,2,3},wobei die Reihenfolge nicht bekannt ist.&amp;lt;br&amp;gt;[[Image:trueFalseBeisp.png|700px]]&amp;lt;br&amp;gt; Also mit Eingabe von drei Elemnten müssen im ungünstigsten Fall drei Schritte vorgenommen werden.&amp;lt;br&amp;gt;Die allgemeine Regel lautet: es gibt N mögliche Lösungen &amp;lt;br&amp;gt;   =&amp;gt;der Baum muss N Blätter haben&amp;lt;br&amp;gt;   =&amp;gt;ein baum mit N Blättern hat mindestens die Höhe logN&amp;lt;br&amp;gt;[[Image:vollbaum.png|left]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
vollständiger Baum (oder balancierter Baum)[http://hci.iwr.uni-heidelberg.de/alda/index.php/Suchen#Balance_eines_Suchbaumes]&amp;lt;br&amp;gt;2^d+1  Knoten&amp;lt;br&amp;gt;2^d  Blätter&amp;lt;br&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;
&lt;br /&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;
&lt;br /&gt;
&lt;br /&gt;
==Sortieren==&lt;br /&gt;
N = n!wenn das Arrey n Elemente hat&amp;lt;br&amp;gt;Zum Beispiel: 3! = 1*2*3 = 6 &amp;lt;br&amp;gt; log6 &amp;lt;math&amp;gt;\approx&amp;lt;/math&amp;gt; 2,6 =&amp;gt; d = 3  -  bei dem Frage-Baum brauch man im ungünstigsten Fall drei Schritte (True/False)&amp;lt;br&amp;gt;log6 &amp;lt;math&amp;gt;\approx&amp;lt;/math&amp;gt; 2,6  -  weil nicht jeder Pfad zu Ende durchgelaufen sein soll, um die Lösung zu bekommen.&amp;lt;br&amp;gt;d&amp;lt;math&amp;gt;\ge&amp;lt;/math&amp;gt;log&amp;lt;math&amp;gt;_2&amp;lt;/math&amp;gt;n! = log&amp;lt;math&amp;gt;_2&amp;lt;/math&amp;gt;(1,2...n) = log&amp;lt;math&amp;gt;_2&amp;lt;/math&amp;gt;1 + log&amp;lt;math&amp;gt;_2&amp;lt;/math&amp;gt;2 + ... + log&amp;lt;math&amp;gt;_2&amp;lt;/math&amp;gt;n = &amp;lt;math&amp;gt;\sum_{n=1}^n log_2n&lt;br /&gt;
&amp;lt;/math&amp;gt;&amp;lt;br&amp;gt; &lt;br /&gt;
===Abschätzung von Summen durch Integrale===&lt;br /&gt;
&lt;br /&gt;
gegeben : f(x) - monoton wachsend &amp;lt;br&amp;gt; [[Image:integralGraph.png|400px|left]]&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\textstyle \int\limits_{x_1}^{x_2} f(\big\lfloor x \big\rfloor)dx&amp;lt;/math&amp;gt;  &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt;\textstyle \int\limits_{x_1}^{x_2} f(x)dx&lt;br /&gt;
&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt;\textstyle \int\limits_{x_1}^{x_2} f(\big\lceil x \big\rceil)dx&amp;lt;/math&amp;gt;  &amp;lt;br&amp;gt; &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;
&lt;br /&gt;
&amp;lt;math&amp;gt;\textstyle \int\limits_{x_1}^{x_1 + 1} \underline{f(x_1)}dx&amp;lt;/math&amp;gt; + ...+ &amp;lt;math&amp;gt;\textstyle \int\limits_{x_2-1}^{x_2} f(x_2 - 1)dx&lt;br /&gt;
&amp;lt;/math&amp;gt; = &amp;lt;math&amp;gt;\sum_{k=x_1}^{x_2-1} f(k)&amp;lt;/math&amp;gt;&amp;lt;br&amp;gt;  ( angenommen &amp;lt;math&amp;gt;x_1&amp;lt;/math&amp;gt; und &amp;lt;math&amp;gt;x_2&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt;\in &amp;lt;/math&amp;gt; &amp;lt;math&amp;gt;\mathbb{N},\mathbb{Z}&amp;lt;/math&amp;gt;&lt;br /&gt;
)&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
wobei f(&amp;lt;math&amp;gt;x_1&amp;lt;/math&amp;gt;) = f(&amp;lt;math&amp;gt;\big\lfloor x \big\rfloor &amp;lt;/math&amp;gt;&amp;lt;math&amp;gt;)_{x_1}^{x_1 +1}&amp;lt;/math&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\textstyle \int\limits_{x_1}^{x_1 +1} f(x_1)dx&amp;lt;/math&amp;gt; = f(&amp;lt;math&amp;gt;x_1&amp;lt;/math&amp;gt;) &amp;lt;math&amp;gt;\textstyle \int\limits_{x_1}^{x_1 + 1} 1dx&amp;lt;/math&amp;gt; = f(&amp;lt;math&amp;gt;x_1&amp;lt;/math&amp;gt;)x&amp;lt;math&amp;gt;\textstyle \int\limits_{x_1}^{x_1 + 1}&amp;lt;/math&amp;gt; = f(x_1)&amp;lt;br&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''&amp;lt;math&amp;gt;\sum_{k=x_1}^{x_2-1} f(k) \le \textstyle \int\limits_{x_1}^{x_2} f(x)dx&amp;lt;/math&amp;gt;'''&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\sum_{k=x_1 +1}^{x_2} f(k) \ge \textstyle \int\limits_{x_1}^{x_2} f(x)dx \iff&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt;\sum_{k=x_1}^{x_2} f(x) \ge \textstyle \int\limits_{x_1 - 1}^{x_2} f(x)dx&lt;br /&gt;
&amp;lt;/math&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''für uns gilt: f(x) = log&amp;lt;math&amp;gt;_2&amp;lt;/math&amp;gt;(x)&amp;lt;br&amp;gt;'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''log&amp;lt;math&amp;gt;_2&amp;lt;/math&amp;gt;1 +''' &amp;lt;math&amp;gt;\sum_{k=2}^{n} log_2 x\ge\textstyle \int\limits_{1}^{n} log_2 (x)dx&lt;br /&gt;
&amp;lt;/math&amp;gt; = &amp;lt;math&amp;gt;\frac{1}{ln2}\textstyle \int\limits_{1}^{n} log(x)dx&amp;lt;/math&amp;gt; = &amp;lt;math&amp;gt;\frac{1}{ln2}&amp;lt;/math&amp;gt; '''[xlogx - x]&amp;lt;math&amp;gt;_{x = 1}^n&amp;lt;/math&amp;gt; = nlog&amp;lt;math&amp;gt;_2&amp;lt;/math&amp;gt;(n) - &amp;lt;math&amp;gt;\frac{n - 1}{ln2}&amp;lt;/math&amp;gt; = &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;(nlog&amp;lt;math&amp;gt;_2&amp;lt;/math&amp;gt;n)''' &lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 '''&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; d = log&amp;lt;math&amp;gt;_2&amp;lt;/math&amp;gt;n! = &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;(nlogn)'''&lt;br /&gt;
kein Sortieralgorithmus auf Basis paarweise Vergleiche ist asymthotisch schneller als Mergesort&lt;br /&gt;
==Prioritätswarteschlangen==&lt;br /&gt;
&lt;br /&gt;
 * Max Priority Quene : insert(x)&lt;br /&gt;
                       x = largest()&lt;br /&gt;
                       removeLargest()&lt;br /&gt;
&lt;br /&gt;
 * Min Priority Quene: smallest()&lt;br /&gt;
                      REMOVEsMALLEST()&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;
        '''    man = 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;
===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 linkssalstigen 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; and 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;/div&gt;</summary>
		<author><name>Friedrich</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1230</id>
		<title>Suchen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1230"/>
				<updated>2008-05-22T17:49:46Z</updated>
		
		<summary type="html">&lt;p&gt;Friedrich: /* Insert */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Es wäre super wenn jemand ganz kurz schreiben könnte, was am Donnerstag zu den Funktionen treeInsert/Remove/HasKey gemacht wurde, was man ja für den aktuellen Übungszettel braucht. Danke :-)&amp;lt;br /&amp;gt;&lt;br /&gt;
/edit: an eine Funktion &amp;quot;HasKey&amp;quot; kann ich mich aus der Vorlesung nicht erinnern. Wenn die jemand hat, bitte eintragen. - Protokollant&lt;br /&gt;
&lt;br /&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;
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 einer oder mehrerer Datensätze aus einer Menge von früher gespeicherten Datensätzen. Ein paar einleitende Worte zum Suchproblem findet man [http://de.wikipedia.org/wiki/Suche hier].&lt;br /&gt;
&lt;br /&gt;
== Überblick verschiedener Suchmethoden ==&lt;br /&gt;
&lt;br /&gt;
Um sich der Vielseitigkeit von Suchproblemen 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 wichtigster 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 einfachst mögliche 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 (Pseudo)-Python-Code zeigt eine Implementation der Suchmethode.&lt;br /&gt;
&lt;br /&gt;
 a = ... # array&lt;br /&gt;
 &lt;br /&gt;
 foundIndex = seqSearch(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;
 def SeqSearch(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 innerste Vergleich (a[i] == key) jeweils &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt; ist (diese Annahme könnte verletzt sein, wenn der Vergleichsoperator überladen ist und dadurch eine höhere Komplexität hat). Dieser Vergleich wird in der for-Schleife jeweils N-mal durchgeführt (&amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt;), so dass man nach der Verschachtelungsregel eine gesamte Komplexität von &amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt; erhält.&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, so ist unter Umständen die Implementierung über einen [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum Binärbaum] (siehe auch weiter unten) geschickter. &lt;br /&gt;
&lt;br /&gt;
Der folgende Algorithmus zeigt eine beispielhafte Implementierung der Methode (wir setzen wieder &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt;):&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;
 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   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    center = (start + end)/2   # Integer Division, 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  # &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&lt;br /&gt;
    else:&lt;br /&gt;
        return binSearch(a, key, start + 1, center)  # Rekursion&lt;br /&gt;
&lt;br /&gt;
Zur Berechnung der Komplexität dieses Algorithmus vernachlässigen wir zunächst den Aufwand, den die Sortierung weiter oben verursacht. Dieser Schritt mag oder mag nicht zulässig sein.&lt;br /&gt;
&lt;br /&gt;
Nach der Sequenzregel haben auch alle &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen 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 und ist &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;
Falls jetzt gilt &amp;lt;math&amp;gt; N = 2^n &amp;lt;/math&amp;gt;&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;
&lt;br /&gt;
== Suche in einem Binärbaum ==&lt;br /&gt;
&lt;br /&gt;
Eine kurze Einführung in Binärbäume findet man [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum hier].&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. Alle Schlüssel im linken Unterbaum sind kleiner, alle Schlüssel im rechten Unterbaum sind größer als ihr Vater. 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 in einem Baum suchen zu können, 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 binärer Suche wie oben 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;
Der folgende Python-Code zeigt beispielhaft, wie man in einem Suchbaum suchen könnte. Der Konstruktor für einen Knoten des Suchbaums ließe sich zum Beispiel so implementieren:&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;
 root = ...    # Wurzel des Suchbaums&lt;br /&gt;
 nodeFound = treeSearch(root, key)   # None, falls nichts gefunden&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:&lt;br /&gt;
         return node&lt;br /&gt;
     elif key &amp;lt; node.key:&lt;br /&gt;
         return treeSearch(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         return treeSearch(node.right, key)&lt;br /&gt;
&lt;br /&gt;
Daraus resultiert der folgende Suchalgorithmus:&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 None:&lt;br /&gt;
     treeSort(node.left, array)     # rekursiv&lt;br /&gt;
     array.append(node.key)     # &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;
 &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{leftleft})+f(N_\mathrm{leftright})+\mathcal{O}(1)+f(N_\mathrm{rightleft})&lt;br /&gt;
 +f(N_\mathrm{leftright})=N\ast\mathcal{O}(1)=\mathcal{O}(N)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sortier-Pseudocode:&lt;br /&gt;
&lt;br /&gt;
 Sortieren:&lt;br /&gt;
     (Array) a     # unsortiert&lt;br /&gt;
     (tree) t     # zunächst leer&lt;br /&gt;
 (dynamisches Array) r     # später sortiert&lt;br /&gt;
 for e in a:&lt;br /&gt;
     t = treeInsert(t, e)&lt;br /&gt;
 treeSort(t, r)&lt;br /&gt;
&lt;br /&gt;
== Insert ==&lt;br /&gt;
&lt;br /&gt;
Was passiert wenn der key (Schlüssel) schon vorhanden ist?&lt;br /&gt;
* error (exception)&lt;br /&gt;
* nichts einfügen&lt;br /&gt;
* nichts einfügen aber einen boolean return zurückgeben (einfügen=true, false)&lt;br /&gt;
* nochmals eingefügt (z.B. in der Node Klasse)&lt;br /&gt;
&lt;br /&gt;
Wobei die ersten 3 Punkte zur Mengensemantik gehören und der letzte eine Multimenge ist.&lt;br /&gt;
&lt;br /&gt;
Algorithmus im zweiten Fall (nichts einfügen):&lt;br /&gt;
&lt;br /&gt;
 def treeInsert(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return Node(key)     # Alternative Schreibweise: node = Node(key)&lt;br /&gt;
     if node.key == key:      # und dann:&lt;br /&gt;
         return node          # pass&lt;br /&gt;
     elif key &amp;lt; node.key:     # links im Baum&lt;br /&gt;
         node.left = treeInsert(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         node.right = treeInsert(node.right, key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Remove ==&lt;br /&gt;
Fälle:&lt;br /&gt;
# key (bzw. Knoten der key enthält) ist ein Blatt =&amp;gt; einfach löschen&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: max k &amp;lt; key (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; keys); Vorgänger ist immer ein Fall 1 oder Fall 2&lt;br /&gt;
#*:=&amp;gt; ersetze node durch Vorgänger und entferne Vorgänger&lt;br /&gt;
&lt;br /&gt;
 def treePredecessor(node):     # wird nur bei Fall 3 aufgerufen&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;
 def treeRemove(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return node&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:&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:&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;
* Pfad (Zwischen 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;): &lt;br /&gt;
**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;), sodass:&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 ein Graph, indem es zwischen beliebigen Knoten stets genau einen Pfad gibt.)&lt;br /&gt;
&lt;br /&gt;
* Länge eines Pfades: Anzahl der Kanten = Anzahl der Knoten - 1&lt;br /&gt;
* Tiefe eines Knotens: Pfadlänge vom Knoten zur Wurzel des Baumes.&lt;br /&gt;
* Tiefe des Baumes: maximale Tiefe eines Knotens&lt;br /&gt;
* Balance eines Baumes: maximale Tiefe(k) - minimale Tiefe(k) (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; Blätter)&lt;br /&gt;
&lt;br /&gt;
== Ungünstigster Fall von treeSearch ==&lt;br /&gt;
&lt;br /&gt;
Komplexität von treeSearch = Länge des Pfades zum Knoten wo &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; gefunden wird, oder erkannt wird, dass &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; nicht im Baum ist.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; wird nicht gefunden, aber für diese Entscheidung muss der Längste Pfad vollständig durchlaufen werden.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(T)&amp;lt;/math&amp;gt; wo T = Tiefe des Baumes&lt;br /&gt;
*: [1,2,3,4,5]:&amp;lt;br /&amp;gt; [[Image:Balance.png]]&lt;br /&gt;
*: Tiefe T = 4, Balance = 4&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; wo N = Anzahl der Elemente.&lt;br /&gt;
&lt;br /&gt;
== Aufgabe ==&lt;br /&gt;
&lt;br /&gt;
Minimiere Balance (erzeuge balancierten Baum):&lt;br /&gt;
# Einfügen in geschickter Reihenfolge (siehe Übungsaufgabe)&lt;br /&gt;
# Selbstbalancierter Baum:&lt;br /&gt;
#* Überprüfen der Balance nach jedem Einfügen&lt;br /&gt;
#* Umstrukturieren des Baumes, falls Balance &amp;gt; 1 (Suchbaum-Bedingung muss erhalten bleiben)&lt;br /&gt;
#* AVL-Bäume (älteste Variante)&lt;br /&gt;
#* Rot-Schwarz-Bäume (verbreiteste Variante)&lt;br /&gt;
#* Treaps (flexibelste Variante, siehe Übung)&lt;br /&gt;
#* Splay trees&lt;br /&gt;
#* Andersson Trees (einfachste Variante)&lt;br /&gt;
(#* Skip Lists (schnellste Variante, aber kein Binärbaum))&lt;br /&gt;
&lt;br /&gt;
== Umstrukturieren, so dass Suchbaumbedingung erhalten bleibt: ==&lt;br /&gt;
&lt;br /&gt;
Rotation: elementare Umstrukturierungen&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum_Rotation.png]]&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;/div&gt;</summary>
		<author><name>Friedrich</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Effizienz&amp;diff=1123</id>
		<title>Effizienz</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Effizienz&amp;diff=1123"/>
				<updated>2008-05-19T20:19:52Z</updated>
		
		<summary type="html">&lt;p&gt;Friedrich: /* Beweis durch Dividieren */&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#Komplexität|Algorithmenkomplexität]] notwendig.&lt;br /&gt;
&lt;br /&gt;
===Optimierung===&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, dass 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*M]&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 aum 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 spart man 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, und man ersetzt eine Division durch eine Multiplikation.&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;
== Komplexität ==&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;
== O-Notation ==&lt;br /&gt;
* Intuitiv: Für große N dominieren die am schnellsten wachsenden Terme.&lt;br /&gt;
* Formale Definition: &lt;br /&gt;
*; Asymptotische Komplexität: Für zwei Funktionen f(x) und g(x) definiert man &lt;br /&gt;
*::&amp;lt;math&amp;gt;f(x) \in \mathcal{O}(g(x))&amp;lt;/math&amp;gt;&lt;br /&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;) 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_0 \ge x: f(x) \le c\,g(x)\}&amp;lt;/math&amp;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;
=== 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)) \land 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 \neq 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:&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;
=== O-Kalkül auf das Beispiel des Selectionsort angewandt ===&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;
&lt;br /&gt;
&amp;lt;!--Sehr geehrter Herr Köthe, der Wiki-Eintrag ist jetzt fertig zum korrigieren. lg, Franziska--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
(Vorlesung 8.5.)&lt;br /&gt;
&lt;br /&gt;
==Beispiel: 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;
! Schritte&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, len(a)):&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;center&amp;gt;O(N-k) = '''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] += 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+1, len(a)):&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;center&amp;gt;O(N-k) = '''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;
Obwohl Version 2 mehr Zeilen ben&amp;amp;ouml;tigt, besitzt das Programm eine geringere Komplexit&amp;amp;auml;t.&lt;br /&gt;
&lt;br /&gt;
===Berechnung der Komplexit&amp;amp;auml;t===&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 Vorlesung 7.5.)&amp;lt;/small&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;u&amp;gt;Zuweisungen&amp;lt;/u&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
äußere Schleife = f(x)&amp;lt;br /&amp;gt;&lt;br /&gt;
innere Schleife = g(x)&lt;br /&gt;
&lt;br /&gt;
4. Schritt: for j in range(k, len(a)) = äußere Schleife &amp;lt;br /&amp;gt;&lt;br /&gt;
:: f(x) = O(N)&amp;lt;small&amp;gt;(siehe Tabelle)&amp;lt;/small&amp;gt;&lt;br /&gt;
5. Schritt: for i in range(j-k+1, j+1) = innere Schleife&amp;lt;br /&amp;gt;&lt;br /&gt;
:: g(x) = O(k)&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;u&amp;gt;Multiplikationsregel (für geschachtelte Schleifen):&amp;lt;/u&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
O(f(x)) * O(g(x)) &amp;amp;isin; O(f(x) * g(x))&amp;lt;br /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;u&amp;gt;Multiplikationsregel angewendet auf Version 1&amp;lt;/u&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
O(N)   * O(k)    &amp;amp;isin; O(N * k)&amp;lt;br /&amp;gt;&lt;br /&gt;
Komplexität von Version 1 = O(N * k)&lt;br /&gt;
&lt;br /&gt;
====Berechnung der Komplexität von Version2====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;small&amp;gt;siehe Tabelle&amp;lt;/small&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;u&amp;gt;Zuweisungen&amp;lt;/u&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
6.Schritt: for j in ange(k+1, len(a)):&amp;lt;br /&amp;gt;&lt;br /&gt;
:: f&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;(x) = O(N)&lt;br /&gt;
&lt;br /&gt;
8.Schritt: for j in range(len(a)):&amp;lt;br /&amp;gt;&lt;br /&gt;
:: f&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(x) = O(N)&lt;br /&gt;
&lt;br /&gt;
4.Schritt: for i in range(k):&amp;lt;br /&amp;gt;&lt;br /&gt;
:: g(x) = O(k)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;u&amp;gt;Additionsregel (für nacheinander ausgeführte Programmteile):&amp;lt;/u&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
O(f(x) + g(x)) = O(f(x)) falls g(x) &amp;amp;isin; O(f(x))&amp;lt;br /&amp;gt;&lt;br /&gt;
O(f(x) + g(x)) &amp;amp;isin; O(g(x)) falls f(x) &amp;amp;isin; O(g(x)) &amp;lt;br /&amp;gt;&lt;br /&gt;
oder kurz O(f(x) + g(x)) = O(max(f(x),g(x)))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;u&amp;gt;Anwendung der Additionsregel auf Version 2&amp;lt;/u&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
O(f&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;(x)) + O(f&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(x)) + O(g(x))&amp;lt;br /&amp;gt;&lt;br /&gt;
O(N) + O(N) +O(k) = O(N), weil O(k) ∈ O(N) [O(max(O(N),O(k)))]&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;small&amp;gt;(da wir wissen O(k) &amp;lt; O(N):&amp;lt;br /&amp;gt;&lt;br /&gt;
if k &amp;gt; len(a):&lt;br /&gt;
:: raise RuntimeError(&amp;quot;k zu groß&amp;quot;)&amp;lt;br /&amp;gt;&lt;br /&gt;
Daraus folgt:&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;amp;rarr; k &amp;lt; len(a) &amp;amp;rarr; for-Schleifen die über k iterieren haben eine kleinere Komplexität als for-Schleifen die über len(a) iterieren)&amp;lt;/small&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Komplexität von Version 2 = O(N)&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 &amp;amp;rarr; höhere Komplexität&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Die gerade berechnete Komplexität gilt &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; unter der Annahme, dass Array-Zugriffe eine Komplexität von O(1) besitzen&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;
|Allgemein:&lt;br /&gt;
Algorithmen-Analysen beruhen auf der Annahme, dass Zugriff auf die Daten optimal schnell sind, dass heißt dass die geeigneteste Datenstruktur verwendetet wird.&amp;lt;br /&amp;gt; &amp;amp;rarr; Ansonsten: Komplexitätsverschlechterung!&lt;br /&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;
&amp;lt;u&amp;gt;Beispiel: Verwende eine verkettete Liste anstalle eines Arrays&amp;lt;/u&amp;gt;. Um auf das j-te Element der Liste zuzugreifen, muss die Liste bis dahin durchlaufen werden&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;left&amp;quot;&lt;br /&gt;
!Implementation von L[j] für verkettete Liste&lt;br /&gt;
!Komplexität&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
: &amp;lt;tt&amp;gt;r = L.head&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
O(1)&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;tt&amp;gt;while j &amp;gt; 0:&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
O(j)&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
:: &amp;lt;tt&amp;gt;r = r.next&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
O(1)&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
:: &amp;lt;tt&amp;gt;j -= 1&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
O(1)&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;tt&amp;gt;return r.data&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
O(1)&lt;br /&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;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''&amp;lt;math&amp;gt; \Bigg\rbrace &amp;lt;/math&amp;gt; O(j)'''&amp;lt;br /&amp;gt;&lt;br /&gt;
r[j] = r[j] + a[i] '''&amp;amp;rarr;''' O(1) &amp;amp;rarr; O(N), falls a[i]= O[N]&lt;br /&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;
====Fazit:====&lt;br /&gt;
Wegen der Schleife hat der Array-artige Zugriff bei einer verketteten Liste eine Komplexität von O(N).&amp;lt;br /&amp;gt;&lt;br /&gt;
Würden wir in unserem running Average-Beispiel&amp;lt;small&amp;gt;(siehe oben)&amp;lt;/small&amp;gt; auf eine verkettete Liste zugreifen hätten wir anstatt O(1) eine Komplexität von O(N) für den Zugriff auf unser Array.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;u&amp;gt;Beispiel: Running-Average mit verketteter Liste&amp;lt;/u&amp;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;
! Version 1 mit verketteter Liste: O(N&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; * 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;&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, len(a)):&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;center&amp;gt;O(N-k) = '''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;
&amp;lt;span style=&amp;quot;color:#00868B&amp;quot;&amp;gt;6.&amp;lt;/span&amp;gt;&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;&amp;lt;span style=&amp;quot;color:#00868B;&amp;quot;&amp;gt;O(i) = '''O(N)'''&amp;lt;/span&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;
&lt;br /&gt;
Die Änderung gegenüber der ursprünglichen Version ist in Zeile 6. Der Zugriff &amp;lt;tt&amp;gt;a[i]&amp;lt;/tt&amp;gt; erfolgt jetzt auf eine Liste, nicht auf ein Array. Aufgrund der obigen Implementation dieses Zugriffs verbirgt sich dahinter ''eine weitere Schleife''. Das heisst, wir haben jetzt '''drei''' geschachtelte Schleifen.&lt;br /&gt;
&amp;lt;u&amp;gt;Zuweisungen&amp;lt;/u&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
äußere Schleife = f&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(x)&amp;lt;br /&amp;gt;&lt;br /&gt;
innere Schleife = g(x)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#00868B&amp;quot;&amp;gt;verkettete Liste&amp;lt;/span&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
:: &amp;lt;span style=&amp;quot;color:#00868B&amp;quot;&amp;gt;f&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;(x) = O(N)&amp;lt;/span&amp;gt;&lt;br /&gt;
4. Schritt: for j in range(k, len(a)) = äußere Schleife &amp;lt;br /&amp;gt;&lt;br /&gt;
:: f&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(x) = O(N)&amp;lt;small&amp;gt;(siehe Tabelle)&amp;lt;/small&amp;gt;&lt;br /&gt;
5. Schritt: for i in range(j-k+1, j+1) = innere Schleife&amp;lt;br /&amp;gt;&lt;br /&gt;
:: g(x) = O(k)&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;u&amp;gt;Multiplikationsregel ohne Konstante:&amp;lt;/u&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
O(f(x) * O(g(x)) &amp;amp;isin; O(f(x) * g(x))&amp;lt;br /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;u&amp;gt;Multiplikationsregel angewendet auf Version 1 mit einer verketteten Liste&amp;lt;/u&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#00868B&amp;quot;&amp;gt;O(N)&amp;lt;/span&amp;gt; * O(N) * O(k) &amp;amp;isin; O(N&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; * k)&amp;lt;br /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&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;
&lt;br /&gt;
&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 runningAverage-Beispiels hätte eine verkettete Liste allerdings keine Auswirkungen, da die Additionsregel bei der Komplexitätsberechnung angewendet würde 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, die Komplexität im schlechtesten Fall schwankt jedoch.&amp;lt;br /&amp;gt;&lt;br /&gt;
Die amortisierte Komplexität beschäftigt sich mit der Komplexität im durschnittlichen/typischen Fall(Average Case)&lt;br /&gt;
&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: Was kostet eine Operation im Durchschnitt?&lt;br /&gt;
&lt;br /&gt;
Annahme: Bei jeder Operation wird ein Gehalt vom Wert 1 bezahlt, dass dem Guthaben zugeschrieben wird, wenn die Kosten das Gehalt decken.&amp;lt;br /&amp;gt;&lt;br /&gt;
:: Kosten &amp;lt;= Gehalt &amp;amp;rarr; es wird gespart&lt;br /&gt;
:: Kosten &amp;gt; Gehalt &amp;amp;rarr; Guthaben - Gehalt werden 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; align=&amp;quot;left&amp;quot;&lt;br /&gt;
!Schritte&lt;br /&gt;
!Zahlen&lt;br /&gt;
!Kosten&lt;br /&gt;
!Kosten + Sparen&lt;br /&gt;
!Guthaben&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;
|}&lt;br /&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;
&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;= Gehalt: 1&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 &amp;gt; Gehalt: 1&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;= Gehalt: 1&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; Gehalt: 1&amp;lt;br /&amp;gt;&lt;br /&gt;
:: &amp;amp;rarr; Guthaben: 2 - Gehalt: 1 = 1&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;
&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 also die Komplexität im schlechtesten (Einzel-)Fall.&lt;br /&gt;
&lt;br /&gt;
In unserem Beispiel fällt der 4. Schritt bei den Kosten schlußendlich nicht so ins Gewicht, da wir die Kosten aus unserem Guthaben mitbezahlen können &amp;amp;rarr; tatsächliche Kosten = 3, Kosten + Sparen = 2&amp;lt;br /&amp;gt;&lt;br /&gt;
:: &amp;amp;rarr; Der Algorithmus besitzt durch dieses Verfahren eine niedrigere Komplexität&lt;br /&gt;
&lt;br /&gt;
==statisches Array==&lt;br /&gt;
&lt;br /&gt;
Ein statisches Array hat eine feste Größe N und besitzt eine Komplexität von O(N).&amp;lt;br /&amp;gt;&lt;br /&gt;
Wird das Array um ein Element erweitert, 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. Es gibt verschiedene Möglichkeiten wie ein dynamisches Array realisiert werden kann.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
capacity = Anzahl der möglichen Elemente, die in das Array passen&amp;lt;br /&amp;gt;&lt;br /&gt;
size = Anzahl der Elemente, die im Array gespeichert sind&amp;lt;br /&amp;gt;&lt;br /&gt;
data = statisches Array der Größe &amp;quot;capacity&amp;quot;&amp;lt;br /&amp;gt;&lt;br /&gt;
A&lt;br /&gt;
&lt;br /&gt;
&amp;lt;u&amp;gt;Beispiele für mögliche Vorgehensweisen eines dynamischen Arrays beim Zufügen eines neuen Elements:&amp;lt;/u&amp;gt; &amp;lt;small&amp;gt;(size == capacity)&amp;lt;/small&amp;gt;&lt;br /&gt;
* Ein neues statisches Array der Größe size == capacity wird erzeugt&lt;br /&gt;
* capacity wird verdoppelt&amp;lt;br /&amp;gt;&lt;br /&gt;
: &amp;amp;rarr; neue capacity = 2 * alte capacity&lt;br /&gt;
* capacity wird um einen Prozentsatz vergrößert&amp;lt;br /&amp;gt;&lt;br /&gt;
: &amp;amp;rarr; neue capacity = alte capacity * c, c &amp;gt; 1&lt;br /&gt;
* ...&lt;br /&gt;
&lt;br /&gt;
'''Folge:''' Das Hinzufügen eines neuen Elements in ein dynamisches Array ist amortisiert, die Operation besitzt eine Komplexität von O(1).&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;&amp;lt;br /&amp;gt;&lt;br /&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;amp;rarr; size &amp;lt; capacity&lt;br /&gt;
&lt;br /&gt;
Kosten: 1&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;br /&amp;gt;&lt;br /&gt;
Potenzial nach append: &amp;amp;Phi;&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; - capacity&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)        - [2(i - 1) - capacity]&lt;br /&gt;
:::::                 = 1            + 2i - capacity         - 2i + 2 + capacity&lt;br /&gt;
:::::                 = 1            + &amp;lt;del&amp;gt;2i&amp;lt;/del&amp;gt; - &amp;lt;del&amp;gt;capacity&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;/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;
Umkopieren wird nach i-tem append benötigt &amp;amp;rarr; size == capacity&lt;br /&gt;
&lt;br /&gt;
Kosten: (i-1) + 1&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;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;br /&amp;gt;&lt;br /&gt;
:::::                = 2i - 2i &amp;lt;small&amp;gt;, da capacity = i&amp;lt;/small&amp;gt;&lt;br /&gt;
:::::                = 0&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) + 0 - [2(i-1) - capacity]&lt;br /&gt;
:::::               = i - 2i - 2 - capacity&lt;br /&gt;
:::::               = &amp;lt;del&amp;gt;i&amp;lt;/del&amp;gt; - &amp;lt;del&amp;gt;2&amp;lt;/del&amp;gt;i - 2 - capacity&lt;br /&gt;
:::::               = &amp;lt;del&amp;gt;i&amp;lt;/del&amp;gt; - 2 - &amp;lt;del&amp;gt;capacity&amp;lt;/del&amp;gt; &amp;lt;small&amp;gt;, da capacity = i&amp;lt;/small&amp;gt;&lt;br /&gt;
:::::               = 2 = O(1) &amp;amp;rarr; konstant            &lt;br /&gt;
&lt;br /&gt;
'''Damit wurde bewiesen, dass die Operation append beim dynamischen Array amortisiert ist &amp;amp;rarr; O(1)'''&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; align=&amp;quot;left&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 1.append&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,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;
== 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'' 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;g\in\mathcal{O}(f)&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;
===&amp;lt;math&amp;gt;\mathcal{O}&amp;lt;/math&amp;gt; - Notation===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f \in \mathcal{O}(g)&amp;lt;/math&amp;gt; ist eine Abschätzung der asymptotischen Komplexität der Funktion f von oben. Per Definition gilt &amp;lt;math&amp;gt;f(N) \in \mathcal{O}(g(N))&amp;lt;/math&amp;gt; genau dann, falls eine Konstante&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; c &amp;gt; 0 &amp;lt;/math&amp;gt; existiert, so dass&lt;br /&gt;
&lt;br /&gt;
&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; &lt;br /&gt;
&lt;br /&gt;
erfüllt ist. &lt;br /&gt;
&lt;br /&gt;
Im Prinzip ist &amp;lt;math&amp;gt;f \in \mathcal{O}(g)&amp;lt;/math&amp;gt; also ein &amp;lt;-Operator für Funktionen, analog zum &amp;lt;-Operator für Zahlen.&lt;br /&gt;
&lt;br /&gt;
===&amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;- Notation===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f \in \Omega(g) &amp;lt;/math&amp;gt; schätzt die asymptotische Komplexität der Funktion f nach unten ab. &lt;br /&gt;
&lt;br /&gt;
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 in der Vorlesung gezeigt werden wird, ist das Sortieren eines Arrays beispielsweise 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 jemals eine geringere Komplexität als Merge-Sort beispielsweise 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 (vergebene) 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 verwendet man jedoch statt der &amp;lt;math&amp;gt;\Theta&amp;lt;/math&amp;gt;- Notation oft die &amp;lt;math&amp;gt;\mathcal{O}&amp;lt;/math&amp;gt; - Notation.&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;
===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 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. Man kann dies als eine alternative Definition von Komplexität ansehen.&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 also 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;/div&gt;</summary>
		<author><name>Friedrich</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Effizienz&amp;diff=1122</id>
		<title>Effizienz</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Effizienz&amp;diff=1122"/>
				<updated>2008-05-19T20:18:19Z</updated>
		
		<summary type="html">&lt;p&gt;Friedrich: /* Beweis durch Dividieren */&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#Komplexität|Algorithmenkomplexität]] notwendig.&lt;br /&gt;
&lt;br /&gt;
===Optimierung===&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, dass 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*M]&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 aum 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 spart man 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, und man ersetzt eine Division durch eine Multiplikation.&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;
== Komplexität ==&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;
== O-Notation ==&lt;br /&gt;
* Intuitiv: Für große N dominieren die am schnellsten wachsenden Terme.&lt;br /&gt;
* Formale Definition: &lt;br /&gt;
*; Asymptotische Komplexität: Für zwei Funktionen f(x) und g(x) definiert man &lt;br /&gt;
*::&amp;lt;math&amp;gt;f(x) \in \mathcal{O}(g(x))&amp;lt;/math&amp;gt;&lt;br /&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;) 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_0 \ge x: f(x) \le c\,g(x)\}&amp;lt;/math&amp;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;
=== 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)) \land 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 \neq 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:&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;
=== O-Kalkül auf das Beispiel des Selectionsort angewandt ===&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;
&lt;br /&gt;
&amp;lt;!--Sehr geehrter Herr Köthe, der Wiki-Eintrag ist jetzt fertig zum korrigieren. lg, Franziska--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
(Vorlesung 8.5.)&lt;br /&gt;
&lt;br /&gt;
==Beispiel: 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;
! Schritte&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, len(a)):&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;center&amp;gt;O(N-k) = '''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] += 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+1, len(a)):&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;center&amp;gt;O(N-k) = '''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;
Obwohl Version 2 mehr Zeilen ben&amp;amp;ouml;tigt, besitzt das Programm eine geringere Komplexit&amp;amp;auml;t.&lt;br /&gt;
&lt;br /&gt;
===Berechnung der Komplexit&amp;amp;auml;t===&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 Vorlesung 7.5.)&amp;lt;/small&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;u&amp;gt;Zuweisungen&amp;lt;/u&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
äußere Schleife = f(x)&amp;lt;br /&amp;gt;&lt;br /&gt;
innere Schleife = g(x)&lt;br /&gt;
&lt;br /&gt;
4. Schritt: for j in range(k, len(a)) = äußere Schleife &amp;lt;br /&amp;gt;&lt;br /&gt;
:: f(x) = O(N)&amp;lt;small&amp;gt;(siehe Tabelle)&amp;lt;/small&amp;gt;&lt;br /&gt;
5. Schritt: for i in range(j-k+1, j+1) = innere Schleife&amp;lt;br /&amp;gt;&lt;br /&gt;
:: g(x) = O(k)&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;u&amp;gt;Multiplikationsregel (für geschachtelte Schleifen):&amp;lt;/u&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
O(f(x)) * O(g(x)) &amp;amp;isin; O(f(x) * g(x))&amp;lt;br /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;u&amp;gt;Multiplikationsregel angewendet auf Version 1&amp;lt;/u&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
O(N)   * O(k)    &amp;amp;isin; O(N * k)&amp;lt;br /&amp;gt;&lt;br /&gt;
Komplexität von Version 1 = O(N * k)&lt;br /&gt;
&lt;br /&gt;
====Berechnung der Komplexität von Version2====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;small&amp;gt;siehe Tabelle&amp;lt;/small&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;u&amp;gt;Zuweisungen&amp;lt;/u&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
6.Schritt: for j in ange(k+1, len(a)):&amp;lt;br /&amp;gt;&lt;br /&gt;
:: f&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;(x) = O(N)&lt;br /&gt;
&lt;br /&gt;
8.Schritt: for j in range(len(a)):&amp;lt;br /&amp;gt;&lt;br /&gt;
:: f&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(x) = O(N)&lt;br /&gt;
&lt;br /&gt;
4.Schritt: for i in range(k):&amp;lt;br /&amp;gt;&lt;br /&gt;
:: g(x) = O(k)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;u&amp;gt;Additionsregel (für nacheinander ausgeführte Programmteile):&amp;lt;/u&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
O(f(x) + g(x)) = O(f(x)) falls g(x) &amp;amp;isin; O(f(x))&amp;lt;br /&amp;gt;&lt;br /&gt;
O(f(x) + g(x)) &amp;amp;isin; O(g(x)) falls f(x) &amp;amp;isin; O(g(x)) &amp;lt;br /&amp;gt;&lt;br /&gt;
oder kurz O(f(x) + g(x)) = O(max(f(x),g(x)))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;u&amp;gt;Anwendung der Additionsregel auf Version 2&amp;lt;/u&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
O(f&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;(x)) + O(f&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(x)) + O(g(x))&amp;lt;br /&amp;gt;&lt;br /&gt;
O(N) + O(N) +O(k) = O(N), weil O(k) ∈ O(N) [O(max(O(N),O(k)))]&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;small&amp;gt;(da wir wissen O(k) &amp;lt; O(N):&amp;lt;br /&amp;gt;&lt;br /&gt;
if k &amp;gt; len(a):&lt;br /&gt;
:: raise RuntimeError(&amp;quot;k zu groß&amp;quot;)&amp;lt;br /&amp;gt;&lt;br /&gt;
Daraus folgt:&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;amp;rarr; k &amp;lt; len(a) &amp;amp;rarr; for-Schleifen die über k iterieren haben eine kleinere Komplexität als for-Schleifen die über len(a) iterieren)&amp;lt;/small&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Komplexität von Version 2 = O(N)&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 &amp;amp;rarr; höhere Komplexität&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Die gerade berechnete Komplexität gilt &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; unter der Annahme, dass Array-Zugriffe eine Komplexität von O(1) besitzen&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;
|Allgemein:&lt;br /&gt;
Algorithmen-Analysen beruhen auf der Annahme, dass Zugriff auf die Daten optimal schnell sind, dass heißt dass die geeigneteste Datenstruktur verwendetet wird.&amp;lt;br /&amp;gt; &amp;amp;rarr; Ansonsten: Komplexitätsverschlechterung!&lt;br /&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;
&amp;lt;u&amp;gt;Beispiel: Verwende eine verkettete Liste anstalle eines Arrays&amp;lt;/u&amp;gt;. Um auf das j-te Element der Liste zuzugreifen, muss die Liste bis dahin durchlaufen werden&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;left&amp;quot;&lt;br /&gt;
!Implementation von L[j] für verkettete Liste&lt;br /&gt;
!Komplexität&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
: &amp;lt;tt&amp;gt;r = L.head&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
O(1)&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;tt&amp;gt;while j &amp;gt; 0:&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
O(j)&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
:: &amp;lt;tt&amp;gt;r = r.next&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
O(1)&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
:: &amp;lt;tt&amp;gt;j -= 1&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
O(1)&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;tt&amp;gt;return r.data&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
O(1)&lt;br /&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;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''&amp;lt;math&amp;gt; \Bigg\rbrace &amp;lt;/math&amp;gt; O(j)'''&amp;lt;br /&amp;gt;&lt;br /&gt;
r[j] = r[j] + a[i] '''&amp;amp;rarr;''' O(1) &amp;amp;rarr; O(N), falls a[i]= O[N]&lt;br /&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;
====Fazit:====&lt;br /&gt;
Wegen der Schleife hat der Array-artige Zugriff bei einer verketteten Liste eine Komplexität von O(N).&amp;lt;br /&amp;gt;&lt;br /&gt;
Würden wir in unserem running Average-Beispiel&amp;lt;small&amp;gt;(siehe oben)&amp;lt;/small&amp;gt; auf eine verkettete Liste zugreifen hätten wir anstatt O(1) eine Komplexität von O(N) für den Zugriff auf unser Array.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;u&amp;gt;Beispiel: Running-Average mit verketteter Liste&amp;lt;/u&amp;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;
! Version 1 mit verketteter Liste: O(N&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; * 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;&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, len(a)):&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;center&amp;gt;O(N-k) = '''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;
&amp;lt;span style=&amp;quot;color:#00868B&amp;quot;&amp;gt;6.&amp;lt;/span&amp;gt;&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;&amp;lt;span style=&amp;quot;color:#00868B;&amp;quot;&amp;gt;O(i) = '''O(N)'''&amp;lt;/span&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;
&lt;br /&gt;
Die Änderung gegenüber der ursprünglichen Version ist in Zeile 6. Der Zugriff &amp;lt;tt&amp;gt;a[i]&amp;lt;/tt&amp;gt; erfolgt jetzt auf eine Liste, nicht auf ein Array. Aufgrund der obigen Implementation dieses Zugriffs verbirgt sich dahinter ''eine weitere Schleife''. Das heisst, wir haben jetzt '''drei''' geschachtelte Schleifen.&lt;br /&gt;
&amp;lt;u&amp;gt;Zuweisungen&amp;lt;/u&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
äußere Schleife = f&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(x)&amp;lt;br /&amp;gt;&lt;br /&gt;
innere Schleife = g(x)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#00868B&amp;quot;&amp;gt;verkettete Liste&amp;lt;/span&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
:: &amp;lt;span style=&amp;quot;color:#00868B&amp;quot;&amp;gt;f&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;(x) = O(N)&amp;lt;/span&amp;gt;&lt;br /&gt;
4. Schritt: for j in range(k, len(a)) = äußere Schleife &amp;lt;br /&amp;gt;&lt;br /&gt;
:: f&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(x) = O(N)&amp;lt;small&amp;gt;(siehe Tabelle)&amp;lt;/small&amp;gt;&lt;br /&gt;
5. Schritt: for i in range(j-k+1, j+1) = innere Schleife&amp;lt;br /&amp;gt;&lt;br /&gt;
:: g(x) = O(k)&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;u&amp;gt;Multiplikationsregel ohne Konstante:&amp;lt;/u&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
O(f(x) * O(g(x)) &amp;amp;isin; O(f(x) * g(x))&amp;lt;br /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;u&amp;gt;Multiplikationsregel angewendet auf Version 1 mit einer verketteten Liste&amp;lt;/u&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#00868B&amp;quot;&amp;gt;O(N)&amp;lt;/span&amp;gt; * O(N) * O(k) &amp;amp;isin; O(N&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; * k)&amp;lt;br /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&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;
&lt;br /&gt;
&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 runningAverage-Beispiels hätte eine verkettete Liste allerdings keine Auswirkungen, da die Additionsregel bei der Komplexitätsberechnung angewendet würde 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, die Komplexität im schlechtesten Fall schwankt jedoch.&amp;lt;br /&amp;gt;&lt;br /&gt;
Die amortisierte Komplexität beschäftigt sich mit der Komplexität im durschnittlichen/typischen Fall(Average Case)&lt;br /&gt;
&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: Was kostet eine Operation im Durchschnitt?&lt;br /&gt;
&lt;br /&gt;
Annahme: Bei jeder Operation wird ein Gehalt vom Wert 1 bezahlt, dass dem Guthaben zugeschrieben wird, wenn die Kosten das Gehalt decken.&amp;lt;br /&amp;gt;&lt;br /&gt;
:: Kosten &amp;lt;= Gehalt &amp;amp;rarr; es wird gespart&lt;br /&gt;
:: Kosten &amp;gt; Gehalt &amp;amp;rarr; Guthaben - Gehalt werden 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; align=&amp;quot;left&amp;quot;&lt;br /&gt;
!Schritte&lt;br /&gt;
!Zahlen&lt;br /&gt;
!Kosten&lt;br /&gt;
!Kosten + Sparen&lt;br /&gt;
!Guthaben&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;
|}&lt;br /&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;
&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;= Gehalt: 1&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 &amp;gt; Gehalt: 1&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;= Gehalt: 1&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; Gehalt: 1&amp;lt;br /&amp;gt;&lt;br /&gt;
:: &amp;amp;rarr; Guthaben: 2 - Gehalt: 1 = 1&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;
&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 also die Komplexität im schlechtesten (Einzel-)Fall.&lt;br /&gt;
&lt;br /&gt;
In unserem Beispiel fällt der 4. Schritt bei den Kosten schlußendlich nicht so ins Gewicht, da wir die Kosten aus unserem Guthaben mitbezahlen können &amp;amp;rarr; tatsächliche Kosten = 3, Kosten + Sparen = 2&amp;lt;br /&amp;gt;&lt;br /&gt;
:: &amp;amp;rarr; Der Algorithmus besitzt durch dieses Verfahren eine niedrigere Komplexität&lt;br /&gt;
&lt;br /&gt;
==statisches Array==&lt;br /&gt;
&lt;br /&gt;
Ein statisches Array hat eine feste Größe N und besitzt eine Komplexität von O(N).&amp;lt;br /&amp;gt;&lt;br /&gt;
Wird das Array um ein Element erweitert, 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. Es gibt verschiedene Möglichkeiten wie ein dynamisches Array realisiert werden kann.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
capacity = Anzahl der möglichen Elemente, die in das Array passen&amp;lt;br /&amp;gt;&lt;br /&gt;
size = Anzahl der Elemente, die im Array gespeichert sind&amp;lt;br /&amp;gt;&lt;br /&gt;
data = statisches Array der Größe &amp;quot;capacity&amp;quot;&amp;lt;br /&amp;gt;&lt;br /&gt;
A&lt;br /&gt;
&lt;br /&gt;
&amp;lt;u&amp;gt;Beispiele für mögliche Vorgehensweisen eines dynamischen Arrays beim Zufügen eines neuen Elements:&amp;lt;/u&amp;gt; &amp;lt;small&amp;gt;(size == capacity)&amp;lt;/small&amp;gt;&lt;br /&gt;
* Ein neues statisches Array der Größe size == capacity wird erzeugt&lt;br /&gt;
* capacity wird verdoppelt&amp;lt;br /&amp;gt;&lt;br /&gt;
: &amp;amp;rarr; neue capacity = 2 * alte capacity&lt;br /&gt;
* capacity wird um einen Prozentsatz vergrößert&amp;lt;br /&amp;gt;&lt;br /&gt;
: &amp;amp;rarr; neue capacity = alte capacity * c, c &amp;gt; 1&lt;br /&gt;
* ...&lt;br /&gt;
&lt;br /&gt;
'''Folge:''' Das Hinzufügen eines neuen Elements in ein dynamisches Array ist amortisiert, die Operation besitzt eine Komplexität von O(1).&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;&amp;lt;br /&amp;gt;&lt;br /&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;amp;rarr; size &amp;lt; capacity&lt;br /&gt;
&lt;br /&gt;
Kosten: 1&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;br /&amp;gt;&lt;br /&gt;
Potenzial nach append: &amp;amp;Phi;&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; - capacity&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)        - [2(i - 1) - capacity]&lt;br /&gt;
:::::                 = 1            + 2i - capacity         - 2i + 2 + capacity&lt;br /&gt;
:::::                 = 1            + &amp;lt;del&amp;gt;2i&amp;lt;/del&amp;gt; - &amp;lt;del&amp;gt;capacity&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;/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;
Umkopieren wird nach i-tem append benötigt &amp;amp;rarr; size == capacity&lt;br /&gt;
&lt;br /&gt;
Kosten: (i-1) + 1&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;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;br /&amp;gt;&lt;br /&gt;
:::::                = 2i - 2i &amp;lt;small&amp;gt;, da capacity = i&amp;lt;/small&amp;gt;&lt;br /&gt;
:::::                = 0&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) + 0 - [2(i-1) - capacity]&lt;br /&gt;
:::::               = i - 2i - 2 - capacity&lt;br /&gt;
:::::               = &amp;lt;del&amp;gt;i&amp;lt;/del&amp;gt; - &amp;lt;del&amp;gt;2&amp;lt;/del&amp;gt;i - 2 - capacity&lt;br /&gt;
:::::               = &amp;lt;del&amp;gt;i&amp;lt;/del&amp;gt; - 2 - &amp;lt;del&amp;gt;capacity&amp;lt;/del&amp;gt; &amp;lt;small&amp;gt;, da capacity = i&amp;lt;/small&amp;gt;&lt;br /&gt;
:::::               = 2 = O(1) &amp;amp;rarr; konstant            &lt;br /&gt;
&lt;br /&gt;
'''Damit wurde bewiesen, dass die Operation append beim dynamischen Array amortisiert ist &amp;amp;rarr; O(1)'''&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; align=&amp;quot;left&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 1.append&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,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;
== 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'' 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;g\in\mathcal{O}(f)&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;
===&amp;lt;math&amp;gt;\mathcal{O}&amp;lt;/math&amp;gt; - Notation===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f \in \mathcal{O}(g)&amp;lt;/math&amp;gt; ist eine Abschätzung der asymptotischen Komplexität der Funktion f von oben. Per Definition gilt &amp;lt;math&amp;gt;f(N) \in \mathcal{O}(g(N))&amp;lt;/math&amp;gt; genau dann, falls eine Konstante&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; c &amp;gt; 0 &amp;lt;/math&amp;gt; existiert, so dass&lt;br /&gt;
&lt;br /&gt;
&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; &lt;br /&gt;
&lt;br /&gt;
erfüllt ist. &lt;br /&gt;
&lt;br /&gt;
Im Prinzip ist &amp;lt;math&amp;gt;f \in \mathcal{O}(g)&amp;lt;/math&amp;gt; also ein &amp;lt;-Operator für Funktionen, analog zum &amp;lt;-Operator für Zahlen.&lt;br /&gt;
&lt;br /&gt;
===&amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;- Notation===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f \in \Omega(g) &amp;lt;/math&amp;gt; schätzt die asymptotische Komplexität der Funktion f nach unten ab. &lt;br /&gt;
&lt;br /&gt;
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 in der Vorlesung gezeigt werden wird, ist das Sortieren eines Arrays beispielsweise 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 jemals eine geringere Komplexität als Merge-Sort beispielsweise 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 (vergebene) 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 verwendet man jedoch statt der &amp;lt;math&amp;gt;\Theta&amp;lt;/math&amp;gt;- Notation oft die &amp;lt;math&amp;gt;\mathcal{O}&amp;lt;/math&amp;gt; - Notation.&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;
===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 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. Man kann dies als eine alternative Definition von Komplexität ansehen.&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 also 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;/div&gt;</summary>
		<author><name>Friedrich</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=File:Baum.png&amp;diff=1121</id>
		<title>File:Baum.png</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=File:Baum.png&amp;diff=1121"/>
				<updated>2008-05-19T20:15:47Z</updated>
		
		<summary type="html">&lt;p&gt;Friedrich: uploaded a new version of &amp;quot;Image:Baum.png&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Friedrich</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1120</id>
		<title>Suchen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1120"/>
				<updated>2008-05-19T20:13:28Z</updated>
		
		<summary type="html">&lt;p&gt;Friedrich: /* Suche in einem Binärbaum */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Es wäre super wenn jemand ganz kurz schreiben könnte, was am Donnerstag zu den Funktionen treeInsert/Remove/HasKey gemacht wurde, was man ja für den aktuellen Übungszettel braucht. Danke :-)&lt;br /&gt;
&lt;br /&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;
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 einer oder mehrerer Datensätze aus einer Menge von früher gespeicherten Datensätzen. Ein paar einleitende Worte zum Suchproblem findet man [http://de.wikipedia.org/wiki/Suche hier].&lt;br /&gt;
&lt;br /&gt;
== Überblick verschiedener Suchmethoden ==&lt;br /&gt;
&lt;br /&gt;
Um sich der Vielseitigkeit von Suchproblemen 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 durch 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 oder das Suchen des nächsten Restaurants.&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 einfachst mögliche 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 (Pseudo)-Python-Code zeigt eine Implementation der Suchmethode.&lt;br /&gt;
&lt;br /&gt;
 a = ... # array&lt;br /&gt;
 len(a) == N&lt;br /&gt;
 foundIndex = seqSearch(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;math&amp;gt; \leq &amp;lt;/math&amp;gt; N wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def SeqSearch(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. &lt;br /&gt;
&lt;br /&gt;
Nimmt man an, dass der innerste Vergleich (a[i] == key) jeweils &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt; ist - was dann sinnvoll ist, wenn der Vergleichsoperator nicht überladen und somit evtl. komplexer ist. Dieser Vergleich wird in der for-Schleife jeweils N-mal durchgeführt (&amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt;), so dass man nach der Verschachtelungsregel eine gesamte Komplexität von &amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt; erhält.&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, so ist unter Umständen die Implementierung über einen [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum Binärbaum]&lt;br /&gt;
(siehe auch weiter unten) geschickter. &lt;br /&gt;
&lt;br /&gt;
Der folgende Algorithmus zeigt eine beispielhafte Implementierung der Methode:&lt;br /&gt;
&lt;br /&gt;
 a = []   # array&lt;br /&gt;
 len(a) == N&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;
&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   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    center = (start + end)/2   # Integer Division, 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  # &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&lt;br /&gt;
    else:&lt;br /&gt;
        return binSearch(a, key, start + 1, center)  # Rekursion&lt;br /&gt;
&lt;br /&gt;
Zur Berechnung der Komplexität dieses Algorithmus vernachlässigen wir zunächst den Aufwand, den die Sortierung weiter oben verursacht. Dieser Schritt mag oder mag nicht zulässig sein.&lt;br /&gt;
&lt;br /&gt;
Nach der Sequenzregel haben auch alle &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen 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 und ist &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;
Falls jetzt gilt &amp;lt;math&amp;gt; N = 2^n &amp;lt;/math&amp;gt;&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 jedoch (&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.&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 anszusehen:&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;2&amp;quot;&lt;br /&gt;
&lt;br /&gt;
! key   !!  start      !! end  !! size !! center !! return  !! Kommentare  &lt;br /&gt;
|-&lt;br /&gt;
| 4     ||0            || 5    ||  5   || 2   ||  2         ||&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;
|-&lt;br /&gt;
| 1     ||0            || 5    ||  5   || 2   ||            || links außerhalb&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 1    ||  1   || 0   ||            ||&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 0    ||  0   ||     || -1         || rechter Randfall&lt;br /&gt;
|-&lt;br /&gt;
| 6     ||0            || 5    ||  5   || 2   ||            ||           &lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    || 2    || 4   || 4          ||&lt;br /&gt;
|-&lt;br /&gt;
| 5     ||0            || 5    ||  5   || 2   ||            || typischer Fall&lt;br /&gt;
|-&lt;br /&gt;
|       ||3            || 5    ||  2   || 4   ||            ||  &lt;br /&gt;
|-&lt;br /&gt;
|       || 3           || 4    ||  1   || 3   || 3          ||&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         ||&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Suche in einem Binärbaum ==&lt;br /&gt;
&lt;br /&gt;
Eine kurze Einführung in Binärbäume findet man [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum hier].&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. Alle Schlüssel im linken Unterbaum sind kleiner, alle Schlüssel im rechten Unterbaum sind größer als ihr Vater. 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 in einem Baum suchen zu können, wollen wir von zwei Annahmen ausgehen:&lt;br /&gt;
# Das Einfügen und Suchen im Baum Baum wechselt sich ab.&lt;br /&gt;
# Der Schlüssel, der die Anordnung bestimmt, kennt eine [http://de.wikipedia.org/wiki/Ordnungsrelation Ordnung] (&amp;lt;-, &amp;gt;-Relation).&lt;br /&gt;
&lt;br /&gt;
Der folgende Python-Code zeigt beispielhaft, wie man in einem Suchbaum suchen könnte. Der Konstruktor für einen Knoten des Suchbaums ließe sich zum Beispiel so implementieren:&lt;br /&gt;
 &lt;br /&gt;
 class Node:&lt;br /&gt;
     def __init__(self, new):&lt;br /&gt;
         self.key = key&lt;br /&gt;
         self.left = self.right = None&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;
 def treeSearch(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return None&lt;br /&gt;
     elif node.key == key:&lt;br /&gt;
         return node&lt;br /&gt;
     elif key &amp;lt; node.key:&lt;br /&gt;
         return treeSearch(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         return treeSearch(node.right, key)&lt;/div&gt;</summary>
		<author><name>Friedrich</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1119</id>
		<title>Suchen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1119"/>
				<updated>2008-05-19T20:12:22Z</updated>
		
		<summary type="html">&lt;p&gt;Friedrich: /* Suche in einem Binärbaum */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Es wäre super wenn jemand ganz kurz schreiben könnte, was am Donnerstag zu den Funktionen treeInsert/Remove/HasKey gemacht wurde, was man ja für den aktuellen Übungszettel braucht. Danke :-)&lt;br /&gt;
&lt;br /&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;
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 einer oder mehrerer Datensätze aus einer Menge von früher gespeicherten Datensätzen. Ein paar einleitende Worte zum Suchproblem findet man [http://de.wikipedia.org/wiki/Suche hier].&lt;br /&gt;
&lt;br /&gt;
== Überblick verschiedener Suchmethoden ==&lt;br /&gt;
&lt;br /&gt;
Um sich der Vielseitigkeit von Suchproblemen 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 durch 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 oder das Suchen des nächsten Restaurants.&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 einfachst mögliche 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 (Pseudo)-Python-Code zeigt eine Implementation der Suchmethode.&lt;br /&gt;
&lt;br /&gt;
 a = ... # array&lt;br /&gt;
 len(a) == N&lt;br /&gt;
 foundIndex = seqSearch(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;math&amp;gt; \leq &amp;lt;/math&amp;gt; N wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def SeqSearch(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. &lt;br /&gt;
&lt;br /&gt;
Nimmt man an, dass der innerste Vergleich (a[i] == key) jeweils &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt; ist - was dann sinnvoll ist, wenn der Vergleichsoperator nicht überladen und somit evtl. komplexer ist. Dieser Vergleich wird in der for-Schleife jeweils N-mal durchgeführt (&amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt;), so dass man nach der Verschachtelungsregel eine gesamte Komplexität von &amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt; erhält.&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, so ist unter Umständen die Implementierung über einen [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum Binärbaum]&lt;br /&gt;
(siehe auch weiter unten) geschickter. &lt;br /&gt;
&lt;br /&gt;
Der folgende Algorithmus zeigt eine beispielhafte Implementierung der Methode:&lt;br /&gt;
&lt;br /&gt;
 a = []   # array&lt;br /&gt;
 len(a) == N&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;
&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   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    center = (start + end)/2   # Integer Division, 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  # &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&lt;br /&gt;
    else:&lt;br /&gt;
        return binSearch(a, key, start + 1, center)  # Rekursion&lt;br /&gt;
&lt;br /&gt;
Zur Berechnung der Komplexität dieses Algorithmus vernachlässigen wir zunächst den Aufwand, den die Sortierung weiter oben verursacht. Dieser Schritt mag oder mag nicht zulässig sein.&lt;br /&gt;
&lt;br /&gt;
Nach der Sequenzregel haben auch alle &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen 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 und ist &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;
Falls jetzt gilt &amp;lt;math&amp;gt; N = 2^n &amp;lt;/math&amp;gt;&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 jedoch (&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.&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 anszusehen:&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;2&amp;quot;&lt;br /&gt;
&lt;br /&gt;
! key   !!  start      !! end  !! size !! center !! return  !! Kommentare  &lt;br /&gt;
|-&lt;br /&gt;
| 4     ||0            || 5    ||  5   || 2   ||  2         ||&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;
|-&lt;br /&gt;
| 1     ||0            || 5    ||  5   || 2   ||            || links außerhalb&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 1    ||  1   || 0   ||            ||&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 0    ||  0   ||     || -1         || rechter Randfall&lt;br /&gt;
|-&lt;br /&gt;
| 6     ||0            || 5    ||  5   || 2   ||            ||           &lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    || 2    || 4   || 4          ||&lt;br /&gt;
|-&lt;br /&gt;
| 5     ||0            || 5    ||  5   || 2   ||            || typischer Fall&lt;br /&gt;
|-&lt;br /&gt;
|       ||3            || 5    ||  2   || 4   ||            ||  &lt;br /&gt;
|-&lt;br /&gt;
|       || 3           || 4    ||  1   || 3   || 3          ||&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         ||&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Suche in einem Binärbaum ==&lt;br /&gt;
&lt;br /&gt;
Eine kurze Einführung in Binärbäume findet man [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum hier].&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. Alle Schlüssel im linken Unterbaum sind kleiner, alle Schlüssel im rechten Unterbaum sind größer als ihr Vater. 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 in einem Baum suchen zu können, wollen wir von zwei Annahmen ausgehen:&lt;br /&gt;
# Das Einfügen und Suchen im Baum Baum wechselt sich ab.&lt;br /&gt;
# Der Schlüssel, der die Anordnung bestimmt, kennt eine [http://de.wikipedia.org/wiki/Ordnungsrelation Ordnung] (&amp;lt;-, &amp;gt;-Relation).&lt;br /&gt;
&lt;br /&gt;
Der folgende Python-Code zeigt beispielhaft, wie man in einem Suchbaum suchen könnte. Der Konstruktor für einen Knoten des Suchbaums ließe sich zum Beispiel so implementieren:&lt;br /&gt;
 &lt;br /&gt;
 class Node:&lt;br /&gt;
     def __init(self, new):&lt;br /&gt;
         self.key = key&lt;br /&gt;
         self.left = self.right = None&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;
 def treeSearch(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return None&lt;br /&gt;
     elif node.key == key:&lt;br /&gt;
         return node&lt;br /&gt;
     elif key &amp;lt; node.key:&lt;br /&gt;
         return treeSearch(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         return treeSearch(node.right, key)&lt;/div&gt;</summary>
		<author><name>Friedrich</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=File:Baum.png&amp;diff=1118</id>
		<title>File:Baum.png</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=File:Baum.png&amp;diff=1118"/>
				<updated>2008-05-19T20:10:38Z</updated>
		
		<summary type="html">&lt;p&gt;Friedrich: uploaded a new version of &amp;quot;Image:Baum.png&amp;quot;: Zur Begriffsillustration von Bäumen&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Friedrich</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=File:Baum.gif&amp;diff=1117</id>
		<title>File:Baum.gif</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=File:Baum.gif&amp;diff=1117"/>
				<updated>2008-05-19T20:04:03Z</updated>
		
		<summary type="html">&lt;p&gt;Friedrich: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Friedrich</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=File:Baum.pdf&amp;diff=1116</id>
		<title>File:Baum.pdf</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=File:Baum.pdf&amp;diff=1116"/>
				<updated>2008-05-19T19:58:35Z</updated>
		
		<summary type="html">&lt;p&gt;Friedrich: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Friedrich</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1101</id>
		<title>Suchen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1101"/>
				<updated>2008-05-18T12:27:13Z</updated>
		
		<summary type="html">&lt;p&gt;Friedrich: /* Suche in einem Binärbaum */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Es wäre super wenn jemand ganz kurz schreiben könnte, was am Donnerstag zu den Funktionen treeInsert/Remove/HasKey gemacht wurde, was man ja für den aktuellen Übungszettel braucht. Danke :-)&lt;br /&gt;
&lt;br /&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;
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 einer oder mehrerer Datensätze aus einer Menge von früher gespeicherten Datensätzen. Ein paar einleitende Worte zum Suchproblem findet man [http://de.wikipedia.org/wiki/Suche hier].&lt;br /&gt;
&lt;br /&gt;
== Überblick verschiedener Suchmethoden ==&lt;br /&gt;
&lt;br /&gt;
Um sich der Vielseitigkeit von Suchproblemen 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 durch 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 oder das Suchen des nächsten Restaurants.&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 einfachst mögliche 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 (Pseudo)-Python-Code zeigt eine Implementation der Suchmethode.&lt;br /&gt;
&lt;br /&gt;
 a = ... # array&lt;br /&gt;
 len(a) == N&lt;br /&gt;
 foundIndex = seqSearch(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;math&amp;gt; \leq &amp;lt;/math&amp;gt; N wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def SeqSearch(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. &lt;br /&gt;
&lt;br /&gt;
Nimmt man an, dass der innerste Vergleich (a[i] == key) jeweils &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt; ist - was dann sinnvoll ist, wenn der Vergleichsoperator nicht überladen und somit evtl. komplexer ist. Dieser Vergleich wird in der for-Schleife jeweils N-mal durchgeführt (&amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt;), so dass man nach der Verschachtelungsregel eine gesamte Komplexität von &amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt; erhält.&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, so ist unter Umständen die Implementierung über einen [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum Binärbaum]&lt;br /&gt;
(siehe auch weiter unten) geschickter. &lt;br /&gt;
&lt;br /&gt;
Der folgende Algorithmus zeigt eine beispielhafte Implementierung der Methode:&lt;br /&gt;
&lt;br /&gt;
 a = []   # array&lt;br /&gt;
 len(a) == N&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;
&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   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    center = (start + end)/2   # Integer Division, 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  # &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&lt;br /&gt;
    else:&lt;br /&gt;
        return binSearch(a, key, start + 1, center)  # Rekursion&lt;br /&gt;
&lt;br /&gt;
Zur Berechnung der Komplexität dieses Algorithmus vernachlässigen wir zunächst den Aufwand, den die Sortierung weiter oben verursacht. Dieser Schritt mag oder mag nicht zulässig sein.&lt;br /&gt;
&lt;br /&gt;
Nach der Sequenzregel haben auch alle &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen 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 und ist &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;
Falls jetzt gilt &amp;lt;math&amp;gt; N = 2^n &amp;lt;/math&amp;gt;&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 jedoch (&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.&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 anszusehen:&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;2&amp;quot;&lt;br /&gt;
&lt;br /&gt;
! key   !!  start      !! end  !! size !! center !! return  !! Kommentare  &lt;br /&gt;
|-&lt;br /&gt;
| 4     ||0            || 5    ||  5   || 2   ||  2         ||&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;
|-&lt;br /&gt;
| 1     ||0            || 5    ||  5   || 2   ||            || links außerhalb&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 1    ||  1   || 0   ||            ||&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 0    ||  0   ||     || -1         || rechter Randfall&lt;br /&gt;
|-&lt;br /&gt;
| 6     ||0            || 5    ||  5   || 2   ||            ||           &lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    || 2    || 4   || 4          ||&lt;br /&gt;
|-&lt;br /&gt;
| 5     ||0            || 5    ||  5   || 2   ||            || typischer Fall&lt;br /&gt;
|-&lt;br /&gt;
|       ||3            || 5    ||  2   || 4   ||            ||  &lt;br /&gt;
|-&lt;br /&gt;
|       || 3           || 4    ||  1   || 3   || 3          ||&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         ||&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Suche in einem Binärbaum ==&lt;br /&gt;
&lt;br /&gt;
Eine kurze Einführung in Binärbäume findet man [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum hier].&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum.png|text-top|200x200px|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. Alle Schlüssel im linken Unterbaum sind kleiner, alle Schlüssel im rechten Unterbaum sind größer als ihr Vater. 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 in einem Baum suchen zu können, wollen wir von zwei Annahmen ausgehen:&lt;br /&gt;
# Das Einfügen und Suchen im Baum Baum wechselt sich ab.&lt;br /&gt;
# Der Schlüssel, der die Anordnung bestimmt, kennt eine [http://de.wikipedia.org/wiki/Ordnungsrelation Ordnung] (&amp;lt;-, &amp;gt;-Relation).&lt;br /&gt;
&lt;br /&gt;
Der folgende Python-Code zeigt beispielhaft, wie man in einem Suchbaum suchen könnte. Der Konstruktor für einen Knoten des Suchbaums ließe sich zum Beispiel so implementieren:&lt;br /&gt;
 &lt;br /&gt;
 class Node:&lt;br /&gt;
     def __init(self, new):&lt;br /&gt;
         self.key = key&lt;br /&gt;
         self.left = self.right = None&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;
 def treeSearch(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return None&lt;br /&gt;
     elif node.key == key:&lt;br /&gt;
         return node&lt;br /&gt;
     elif key &amp;lt; node.key:&lt;br /&gt;
         return treeSearch(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         return treeSearch(node.right, key)&lt;/div&gt;</summary>
		<author><name>Friedrich</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1100</id>
		<title>Suchen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1100"/>
				<updated>2008-05-18T12:11:03Z</updated>
		
		<summary type="html">&lt;p&gt;Friedrich: /* Suche in einem Binärbaum */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Es wäre super wenn jemand ganz kurz schreiben könnte, was am Donnerstag zu den Funktionen treeInsert/Remove/HasKey gemacht wurde, was man ja für den aktuellen Übungszettel braucht. Danke :-)&lt;br /&gt;
&lt;br /&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;
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 einer oder mehrerer Datensätze aus einer Menge von früher gespeicherten Datensätzen. Ein paar einleitende Worte zum Suchproblem findet man [http://de.wikipedia.org/wiki/Suche hier].&lt;br /&gt;
&lt;br /&gt;
== Überblick verschiedener Suchmethoden ==&lt;br /&gt;
&lt;br /&gt;
Um sich der Vielseitigkeit von Suchproblemen 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 durch 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 oder das Suchen des nächsten Restaurants.&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 einfachst mögliche 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 (Pseudo)-Python-Code zeigt eine Implementation der Suchmethode.&lt;br /&gt;
&lt;br /&gt;
 a = ... # array&lt;br /&gt;
 len(a) == N&lt;br /&gt;
 foundIndex = seqSearch(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;math&amp;gt; \leq &amp;lt;/math&amp;gt; N wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def SeqSearch(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. &lt;br /&gt;
&lt;br /&gt;
Nimmt man an, dass der innerste Vergleich (a[i] == key) jeweils &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt; ist - was dann sinnvoll ist, wenn der Vergleichsoperator nicht überladen und somit evtl. komplexer ist. Dieser Vergleich wird in der for-Schleife jeweils N-mal durchgeführt (&amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt;), so dass man nach der Verschachtelungsregel eine gesamte Komplexität von &amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt; erhält.&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, so ist unter Umständen die Implementierung über einen [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum Binärbaum]&lt;br /&gt;
(siehe auch weiter unten) geschickter. &lt;br /&gt;
&lt;br /&gt;
Der folgende Algorithmus zeigt eine beispielhafte Implementierung der Methode:&lt;br /&gt;
&lt;br /&gt;
 a = []   # array&lt;br /&gt;
 len(a) == N&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;
&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   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    center = (start + end)/2   # Integer Division, 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  # &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&lt;br /&gt;
    else:&lt;br /&gt;
        return binSearch(a, key, start + 1, center)  # Rekursion&lt;br /&gt;
&lt;br /&gt;
Zur Berechnung der Komplexität dieses Algorithmus vernachlässigen wir zunächst den Aufwand, den die Sortierung weiter oben verursacht. Dieser Schritt mag oder mag nicht zulässig sein.&lt;br /&gt;
&lt;br /&gt;
Nach der Sequenzregel haben auch alle &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen 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 und ist &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;
Falls jetzt gilt &amp;lt;math&amp;gt; N = 2^n &amp;lt;/math&amp;gt;&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 jedoch (&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.&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 anszusehen:&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;2&amp;quot;&lt;br /&gt;
&lt;br /&gt;
! key   !!  start      !! end  !! size !! center !! return  !! Kommentare  &lt;br /&gt;
|-&lt;br /&gt;
| 4     ||0            || 5    ||  5   || 2   ||  2         ||&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;
|-&lt;br /&gt;
| 1     ||0            || 5    ||  5   || 2   ||            || links außerhalb&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 1    ||  1   || 0   ||            ||&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 0    ||  0   ||     || -1         || rechter Randfall&lt;br /&gt;
|-&lt;br /&gt;
| 6     ||0            || 5    ||  5   || 2   ||            ||           &lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    || 2    || 4   || 4          ||&lt;br /&gt;
|-&lt;br /&gt;
| 5     ||0            || 5    ||  5   || 2   ||            || typischer Fall&lt;br /&gt;
|-&lt;br /&gt;
|       ||3            || 5    ||  2   || 4   ||            ||  &lt;br /&gt;
|-&lt;br /&gt;
|       || 3           || 4    ||  1   || 3   || 3          ||&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         ||&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Suche in einem Binärbaum ==&lt;br /&gt;
&lt;br /&gt;
Eine kurze Einführung in Binärbäume findet man [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum hier].&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum.png|text-top|200x200px|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. Alle Schlüssel im linken Unterbaum sind kleiner, alle Schlüssel im rechten Unterbaum sind größer als ihr Vater. 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 in einem Baum suchen zu können, wollen wir von zwei Annahmen ausgehen:&lt;br /&gt;
# Das Einfügen und Suchen im Baum Baum wechselt sich ab.&lt;br /&gt;
# Der Schlüssel, der die Anordnung bestimmt, kennt eine [http://de.wikipedia.org/wiki/Ordnungsrelation Ordnung] (&amp;lt;-, &amp;gt;-Relation).&lt;/div&gt;</summary>
		<author><name>Friedrich</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1099</id>
		<title>Suchen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1099"/>
				<updated>2008-05-18T11:58:21Z</updated>
		
		<summary type="html">&lt;p&gt;Friedrich: /* Suche in einem Binärbaum */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Es wäre super wenn jemand ganz kurz schreiben könnte, was am Donnerstag zu den Funktionen treeInsert/Remove/HasKey gemacht wurde, was man ja für den aktuellen Übungszettel braucht. Danke :-)&lt;br /&gt;
&lt;br /&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;
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 einer oder mehrerer Datensätze aus einer Menge von früher gespeicherten Datensätzen. Ein paar einleitende Worte zum Suchproblem findet man [http://de.wikipedia.org/wiki/Suche hier].&lt;br /&gt;
&lt;br /&gt;
== Überblick verschiedener Suchmethoden ==&lt;br /&gt;
&lt;br /&gt;
Um sich der Vielseitigkeit von Suchproblemen 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 durch 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 oder das Suchen des nächsten Restaurants.&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 einfachst mögliche 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 (Pseudo)-Python-Code zeigt eine Implementation der Suchmethode.&lt;br /&gt;
&lt;br /&gt;
 a = ... # array&lt;br /&gt;
 len(a) == N&lt;br /&gt;
 foundIndex = seqSearch(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;math&amp;gt; \leq &amp;lt;/math&amp;gt; N wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def SeqSearch(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. &lt;br /&gt;
&lt;br /&gt;
Nimmt man an, dass der innerste Vergleich (a[i] == key) jeweils &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt; ist - was dann sinnvoll ist, wenn der Vergleichsoperator nicht überladen und somit evtl. komplexer ist. Dieser Vergleich wird in der for-Schleife jeweils N-mal durchgeführt (&amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt;), so dass man nach der Verschachtelungsregel eine gesamte Komplexität von &amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt; erhält.&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, so ist unter Umständen die Implementierung über einen [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum Binärbaum]&lt;br /&gt;
(siehe auch weiter unten) geschickter. &lt;br /&gt;
&lt;br /&gt;
Der folgende Algorithmus zeigt eine beispielhafte Implementierung der Methode:&lt;br /&gt;
&lt;br /&gt;
 a = []   # array&lt;br /&gt;
 len(a) == N&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;
&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   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    center = (start + end)/2   # Integer Division, 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  # &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&lt;br /&gt;
    else:&lt;br /&gt;
        return binSearch(a, key, start + 1, center)  # Rekursion&lt;br /&gt;
&lt;br /&gt;
Zur Berechnung der Komplexität dieses Algorithmus vernachlässigen wir zunächst den Aufwand, den die Sortierung weiter oben verursacht. Dieser Schritt mag oder mag nicht zulässig sein.&lt;br /&gt;
&lt;br /&gt;
Nach der Sequenzregel haben auch alle &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen 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 und ist &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;
Falls jetzt gilt &amp;lt;math&amp;gt; N = 2^n &amp;lt;/math&amp;gt;&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 jedoch (&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.&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 anszusehen:&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;2&amp;quot;&lt;br /&gt;
&lt;br /&gt;
! key   !!  start      !! end  !! size !! center !! return  !! Kommentare  &lt;br /&gt;
|-&lt;br /&gt;
| 4     ||0            || 5    ||  5   || 2   ||  2         ||&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;
|-&lt;br /&gt;
| 1     ||0            || 5    ||  5   || 2   ||            || links außerhalb&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 1    ||  1   || 0   ||            ||&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 0    ||  0   ||     || -1         || rechter Randfall&lt;br /&gt;
|-&lt;br /&gt;
| 6     ||0            || 5    ||  5   || 2   ||            ||           &lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    || 2    || 4   || 4          ||&lt;br /&gt;
|-&lt;br /&gt;
| 5     ||0            || 5    ||  5   || 2   ||            || typischer Fall&lt;br /&gt;
|-&lt;br /&gt;
|       ||3            || 5    ||  2   || 4   ||            ||  &lt;br /&gt;
|-&lt;br /&gt;
|       || 3           || 4    ||  1   || 3   || 3          ||&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         ||&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Suche in einem Binärbaum ==&lt;br /&gt;
&lt;br /&gt;
Eine kurze Einführung in Binärbäume findet man [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum hier].&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum.png|text-top|200x200px|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, 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''&lt;/div&gt;</summary>
		<author><name>Friedrich</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1098</id>
		<title>Suchen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1098"/>
				<updated>2008-05-18T11:46:31Z</updated>
		
		<summary type="html">&lt;p&gt;Friedrich: /* Suche in einem Binärbaum */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Es wäre super wenn jemand ganz kurz schreiben könnte, was am Donnerstag zu den Funktionen treeInsert/Remove/HasKey gemacht wurde, was man ja für den aktuellen Übungszettel braucht. Danke :-)&lt;br /&gt;
&lt;br /&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;
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 einer oder mehrerer Datensätze aus einer Menge von früher gespeicherten Datensätzen. Ein paar einleitende Worte zum Suchproblem findet man [http://de.wikipedia.org/wiki/Suche hier].&lt;br /&gt;
&lt;br /&gt;
== Überblick verschiedener Suchmethoden ==&lt;br /&gt;
&lt;br /&gt;
Um sich der Vielseitigkeit von Suchproblemen 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 durch 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 oder das Suchen des nächsten Restaurants.&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 einfachst mögliche 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 (Pseudo)-Python-Code zeigt eine Implementation der Suchmethode.&lt;br /&gt;
&lt;br /&gt;
 a = ... # array&lt;br /&gt;
 len(a) == N&lt;br /&gt;
 foundIndex = seqSearch(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;math&amp;gt; \leq &amp;lt;/math&amp;gt; N wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def SeqSearch(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. &lt;br /&gt;
&lt;br /&gt;
Nimmt man an, dass der innerste Vergleich (a[i] == key) jeweils &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt; ist - was dann sinnvoll ist, wenn der Vergleichsoperator nicht überladen und somit evtl. komplexer ist. Dieser Vergleich wird in der for-Schleife jeweils N-mal durchgeführt (&amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt;), so dass man nach der Verschachtelungsregel eine gesamte Komplexität von &amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt; erhält.&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, so ist unter Umständen die Implementierung über einen [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum Binärbaum]&lt;br /&gt;
(siehe auch weiter unten) geschickter. &lt;br /&gt;
&lt;br /&gt;
Der folgende Algorithmus zeigt eine beispielhafte Implementierung der Methode:&lt;br /&gt;
&lt;br /&gt;
 a = []   # array&lt;br /&gt;
 len(a) == N&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;
&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   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    center = (start + end)/2   # Integer Division, 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  # &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&lt;br /&gt;
    else:&lt;br /&gt;
        return binSearch(a, key, start + 1, center)  # Rekursion&lt;br /&gt;
&lt;br /&gt;
Zur Berechnung der Komplexität dieses Algorithmus vernachlässigen wir zunächst den Aufwand, den die Sortierung weiter oben verursacht. Dieser Schritt mag oder mag nicht zulässig sein.&lt;br /&gt;
&lt;br /&gt;
Nach der Sequenzregel haben auch alle &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen 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 und ist &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;
Falls jetzt gilt &amp;lt;math&amp;gt; N = 2^n &amp;lt;/math&amp;gt;&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 jedoch (&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.&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 anszusehen:&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;2&amp;quot;&lt;br /&gt;
&lt;br /&gt;
! key   !!  start      !! end  !! size !! center !! return  !! Kommentare  &lt;br /&gt;
|-&lt;br /&gt;
| 4     ||0            || 5    ||  5   || 2   ||  2         ||&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;
|-&lt;br /&gt;
| 1     ||0            || 5    ||  5   || 2   ||            || links außerhalb&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 1    ||  1   || 0   ||            ||&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 0    ||  0   ||     || -1         || rechter Randfall&lt;br /&gt;
|-&lt;br /&gt;
| 6     ||0            || 5    ||  5   || 2   ||            ||           &lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    || 2    || 4   || 4          ||&lt;br /&gt;
|-&lt;br /&gt;
| 5     ||0            || 5    ||  5   || 2   ||            || typischer Fall&lt;br /&gt;
|-&lt;br /&gt;
|       ||3            || 5    ||  2   || 4   ||            ||  &lt;br /&gt;
|-&lt;br /&gt;
|       || 3           || 4    ||  1   || 3   || 3          ||&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         ||&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Suche in einem Binärbaum ==&lt;br /&gt;
&lt;br /&gt;
Eine kurze Einführung in Binärbäume findet man [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum hier].&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum.png|text-top|200x200px|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, eignen sich Bäume also insbesondere, um gesuchte Daten schnell wieder auffinden zu können.&lt;/div&gt;</summary>
		<author><name>Friedrich</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1097</id>
		<title>Suchen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1097"/>
				<updated>2008-05-18T11:36:37Z</updated>
		
		<summary type="html">&lt;p&gt;Friedrich: /* Suche in einem Binärbaum */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Es wäre super wenn jemand ganz kurz schreiben könnte, was am Donnerstag zu den Funktionen treeInsert/Remove/HasKey gemacht wurde, was man ja für den aktuellen Übungszettel braucht. Danke :-)&lt;br /&gt;
&lt;br /&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;
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 einer oder mehrerer Datensätze aus einer Menge von früher gespeicherten Datensätzen. Ein paar einleitende Worte zum Suchproblem findet man [http://de.wikipedia.org/wiki/Suche hier].&lt;br /&gt;
&lt;br /&gt;
== Überblick verschiedener Suchmethoden ==&lt;br /&gt;
&lt;br /&gt;
Um sich der Vielseitigkeit von Suchproblemen 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 durch 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 oder das Suchen des nächsten Restaurants.&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 einfachst mögliche 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 (Pseudo)-Python-Code zeigt eine Implementation der Suchmethode.&lt;br /&gt;
&lt;br /&gt;
 a = ... # array&lt;br /&gt;
 len(a) == N&lt;br /&gt;
 foundIndex = seqSearch(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;math&amp;gt; \leq &amp;lt;/math&amp;gt; N wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def SeqSearch(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. &lt;br /&gt;
&lt;br /&gt;
Nimmt man an, dass der innerste Vergleich (a[i] == key) jeweils &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt; ist - was dann sinnvoll ist, wenn der Vergleichsoperator nicht überladen und somit evtl. komplexer ist. Dieser Vergleich wird in der for-Schleife jeweils N-mal durchgeführt (&amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt;), so dass man nach der Verschachtelungsregel eine gesamte Komplexität von &amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt; erhält.&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, so ist unter Umständen die Implementierung über einen [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum Binärbaum]&lt;br /&gt;
(siehe auch weiter unten) geschickter. &lt;br /&gt;
&lt;br /&gt;
Der folgende Algorithmus zeigt eine beispielhafte Implementierung der Methode:&lt;br /&gt;
&lt;br /&gt;
 a = []   # array&lt;br /&gt;
 len(a) == N&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;
&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   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    center = (start + end)/2   # Integer Division, 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  # &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&lt;br /&gt;
    else:&lt;br /&gt;
        return binSearch(a, key, start + 1, center)  # Rekursion&lt;br /&gt;
&lt;br /&gt;
Zur Berechnung der Komplexität dieses Algorithmus vernachlässigen wir zunächst den Aufwand, den die Sortierung weiter oben verursacht. Dieser Schritt mag oder mag nicht zulässig sein.&lt;br /&gt;
&lt;br /&gt;
Nach der Sequenzregel haben auch alle &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen 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 und ist &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;
Falls jetzt gilt &amp;lt;math&amp;gt; N = 2^n &amp;lt;/math&amp;gt;&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 jedoch (&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.&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 anszusehen:&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;2&amp;quot;&lt;br /&gt;
&lt;br /&gt;
! key   !!  start      !! end  !! size !! center !! return  !! Kommentare  &lt;br /&gt;
|-&lt;br /&gt;
| 4     ||0            || 5    ||  5   || 2   ||  2         ||&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;
|-&lt;br /&gt;
| 1     ||0            || 5    ||  5   || 2   ||            || links außerhalb&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 1    ||  1   || 0   ||            ||&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 0    ||  0   ||     || -1         || rechter Randfall&lt;br /&gt;
|-&lt;br /&gt;
| 6     ||0            || 5    ||  5   || 2   ||            ||           &lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    || 2    || 4   || 4          ||&lt;br /&gt;
|-&lt;br /&gt;
| 5     ||0            || 5    ||  5   || 2   ||            || typischer Fall&lt;br /&gt;
|-&lt;br /&gt;
|       ||3            || 5    ||  2   || 4   ||            ||  &lt;br /&gt;
|-&lt;br /&gt;
|       || 3           || 4    ||  1   || 3   || 3          ||&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         ||&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Suche in einem Binärbaum ==&lt;br /&gt;
&lt;br /&gt;
Eine kurze Einführung in Binärbäume findet man [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum hier].&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum.png|text-top|200x200px|Zur Illustration von Bäumen]]&lt;/div&gt;</summary>
		<author><name>Friedrich</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1096</id>
		<title>Suchen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1096"/>
				<updated>2008-05-18T11:33:20Z</updated>
		
		<summary type="html">&lt;p&gt;Friedrich: /* Suche in einem Binärbaum */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Es wäre super wenn jemand ganz kurz schreiben könnte, was am Donnerstag zu den Funktionen treeInsert/Remove/HasKey gemacht wurde, was man ja für den aktuellen Übungszettel braucht. Danke :-)&lt;br /&gt;
&lt;br /&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;
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 einer oder mehrerer Datensätze aus einer Menge von früher gespeicherten Datensätzen. Ein paar einleitende Worte zum Suchproblem findet man [http://de.wikipedia.org/wiki/Suche hier].&lt;br /&gt;
&lt;br /&gt;
== Überblick verschiedener Suchmethoden ==&lt;br /&gt;
&lt;br /&gt;
Um sich der Vielseitigkeit von Suchproblemen 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 durch 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 oder das Suchen des nächsten Restaurants.&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 einfachst mögliche 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 (Pseudo)-Python-Code zeigt eine Implementation der Suchmethode.&lt;br /&gt;
&lt;br /&gt;
 a = ... # array&lt;br /&gt;
 len(a) == N&lt;br /&gt;
 foundIndex = seqSearch(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;math&amp;gt; \leq &amp;lt;/math&amp;gt; N wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def SeqSearch(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. &lt;br /&gt;
&lt;br /&gt;
Nimmt man an, dass der innerste Vergleich (a[i] == key) jeweils &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt; ist - was dann sinnvoll ist, wenn der Vergleichsoperator nicht überladen und somit evtl. komplexer ist. Dieser Vergleich wird in der for-Schleife jeweils N-mal durchgeführt (&amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt;), so dass man nach der Verschachtelungsregel eine gesamte Komplexität von &amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt; erhält.&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, so ist unter Umständen die Implementierung über einen [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum Binärbaum]&lt;br /&gt;
(siehe auch weiter unten) geschickter. &lt;br /&gt;
&lt;br /&gt;
Der folgende Algorithmus zeigt eine beispielhafte Implementierung der Methode:&lt;br /&gt;
&lt;br /&gt;
 a = []   # array&lt;br /&gt;
 len(a) == N&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;
&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   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    center = (start + end)/2   # Integer Division, 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  # &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&lt;br /&gt;
    else:&lt;br /&gt;
        return binSearch(a, key, start + 1, center)  # Rekursion&lt;br /&gt;
&lt;br /&gt;
Zur Berechnung der Komplexität dieses Algorithmus vernachlässigen wir zunächst den Aufwand, den die Sortierung weiter oben verursacht. Dieser Schritt mag oder mag nicht zulässig sein.&lt;br /&gt;
&lt;br /&gt;
Nach der Sequenzregel haben auch alle &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen 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 und ist &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;
Falls jetzt gilt &amp;lt;math&amp;gt; N = 2^n &amp;lt;/math&amp;gt;&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 jedoch (&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.&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 anszusehen:&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;2&amp;quot;&lt;br /&gt;
&lt;br /&gt;
! key   !!  start      !! end  !! size !! center !! return  !! Kommentare  &lt;br /&gt;
|-&lt;br /&gt;
| 4     ||0            || 5    ||  5   || 2   ||  2         ||&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;
|-&lt;br /&gt;
| 1     ||0            || 5    ||  5   || 2   ||            || links außerhalb&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 1    ||  1   || 0   ||            ||&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 0    ||  0   ||     || -1         || rechter Randfall&lt;br /&gt;
|-&lt;br /&gt;
| 6     ||0            || 5    ||  5   || 2   ||            ||           &lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    || 2    || 4   || 4          ||&lt;br /&gt;
|-&lt;br /&gt;
| 5     ||0            || 5    ||  5   || 2   ||            || typischer Fall&lt;br /&gt;
|-&lt;br /&gt;
|       ||3            || 5    ||  2   || 4   ||            ||  &lt;br /&gt;
|-&lt;br /&gt;
|       || 3           || 4    ||  1   || 3   || 3          ||&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         ||&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Suche in einem Binärbaum ==&lt;br /&gt;
&lt;br /&gt;
Eine kurze Einführung in Binärbäume findet man [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum hier].&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum.png|framed|thumb|200x200px|Baum]]&lt;/div&gt;</summary>
		<author><name>Friedrich</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1095</id>
		<title>Suchen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1095"/>
				<updated>2008-05-18T11:31:54Z</updated>
		
		<summary type="html">&lt;p&gt;Friedrich: /* Suche in einem Binärbaum */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Es wäre super wenn jemand ganz kurz schreiben könnte, was am Donnerstag zu den Funktionen treeInsert/Remove/HasKey gemacht wurde, was man ja für den aktuellen Übungszettel braucht. Danke :-)&lt;br /&gt;
&lt;br /&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;
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 einer oder mehrerer Datensätze aus einer Menge von früher gespeicherten Datensätzen. Ein paar einleitende Worte zum Suchproblem findet man [http://de.wikipedia.org/wiki/Suche hier].&lt;br /&gt;
&lt;br /&gt;
== Überblick verschiedener Suchmethoden ==&lt;br /&gt;
&lt;br /&gt;
Um sich der Vielseitigkeit von Suchproblemen 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 durch 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 oder das Suchen des nächsten Restaurants.&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 einfachst mögliche 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 (Pseudo)-Python-Code zeigt eine Implementation der Suchmethode.&lt;br /&gt;
&lt;br /&gt;
 a = ... # array&lt;br /&gt;
 len(a) == N&lt;br /&gt;
 foundIndex = seqSearch(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;math&amp;gt; \leq &amp;lt;/math&amp;gt; N wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def SeqSearch(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. &lt;br /&gt;
&lt;br /&gt;
Nimmt man an, dass der innerste Vergleich (a[i] == key) jeweils &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt; ist - was dann sinnvoll ist, wenn der Vergleichsoperator nicht überladen und somit evtl. komplexer ist. Dieser Vergleich wird in der for-Schleife jeweils N-mal durchgeführt (&amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt;), so dass man nach der Verschachtelungsregel eine gesamte Komplexität von &amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt; erhält.&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, so ist unter Umständen die Implementierung über einen [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum Binärbaum]&lt;br /&gt;
(siehe auch weiter unten) geschickter. &lt;br /&gt;
&lt;br /&gt;
Der folgende Algorithmus zeigt eine beispielhafte Implementierung der Methode:&lt;br /&gt;
&lt;br /&gt;
 a = []   # array&lt;br /&gt;
 len(a) == N&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;
&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   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    center = (start + end)/2   # Integer Division, 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  # &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&lt;br /&gt;
    else:&lt;br /&gt;
        return binSearch(a, key, start + 1, center)  # Rekursion&lt;br /&gt;
&lt;br /&gt;
Zur Berechnung der Komplexität dieses Algorithmus vernachlässigen wir zunächst den Aufwand, den die Sortierung weiter oben verursacht. Dieser Schritt mag oder mag nicht zulässig sein.&lt;br /&gt;
&lt;br /&gt;
Nach der Sequenzregel haben auch alle &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen 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 und ist &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;
Falls jetzt gilt &amp;lt;math&amp;gt; N = 2^n &amp;lt;/math&amp;gt;&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 jedoch (&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.&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 anszusehen:&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;2&amp;quot;&lt;br /&gt;
&lt;br /&gt;
! key   !!  start      !! end  !! size !! center !! return  !! Kommentare  &lt;br /&gt;
|-&lt;br /&gt;
| 4     ||0            || 5    ||  5   || 2   ||  2         ||&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;
|-&lt;br /&gt;
| 1     ||0            || 5    ||  5   || 2   ||            || links außerhalb&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 1    ||  1   || 0   ||            ||&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 0    ||  0   ||     || -1         || rechter Randfall&lt;br /&gt;
|-&lt;br /&gt;
| 6     ||0            || 5    ||  5   || 2   ||            ||           &lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    || 2    || 4   || 4          ||&lt;br /&gt;
|-&lt;br /&gt;
| 5     ||0            || 5    ||  5   || 2   ||            || typischer Fall&lt;br /&gt;
|-&lt;br /&gt;
|       ||3            || 5    ||  2   || 4   ||            ||  &lt;br /&gt;
|-&lt;br /&gt;
|       || 3           || 4    ||  1   || 3   || 3          ||&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         ||&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Suche in einem Binärbaum ==&lt;br /&gt;
&lt;br /&gt;
Eine kurze Einführung in Binärbäume findet man [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum hier].&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum.png|thumb|100x100px|Baum]]&lt;/div&gt;</summary>
		<author><name>Friedrich</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=File:Baum.png&amp;diff=1094</id>
		<title>File:Baum.png</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=File:Baum.png&amp;diff=1094"/>
				<updated>2008-05-18T11:27:02Z</updated>
		
		<summary type="html">&lt;p&gt;Friedrich: uploaded a new version of &amp;quot;Image:Baum.png&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Friedrich</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=File:Baum.png&amp;diff=1093</id>
		<title>File:Baum.png</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=File:Baum.png&amp;diff=1093"/>
				<updated>2008-05-18T11:26:22Z</updated>
		
		<summary type="html">&lt;p&gt;Friedrich: uploaded a new version of &amp;quot;Image:Baum.png&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Friedrich</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=File:Baum.png&amp;diff=1092</id>
		<title>File:Baum.png</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=File:Baum.png&amp;diff=1092"/>
				<updated>2008-05-18T11:17:43Z</updated>
		
		<summary type="html">&lt;p&gt;Friedrich: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Friedrich</name></author>	</entry>

	</feed>