<?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=Tgoeckel</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=Tgoeckel"/>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php/Special:Contributions/Tgoeckel"/>
		<updated>2026-05-08T18:22:35Z</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=1229</id>
		<title>Suchen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1229"/>
				<updated>2008-05-22T14:04:54Z</updated>
		
		<summary type="html">&lt;p&gt;Tgoeckel: /* Umstrukturieren, so dass Suchbaumbedingung erhalten bleibt: */&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>Tgoeckel</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=File:Baum_Rotation.png&amp;diff=1228</id>
		<title>File:Baum Rotation.png</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=File:Baum_Rotation.png&amp;diff=1228"/>
				<updated>2008-05-22T14:04:08Z</updated>
		
		<summary type="html">&lt;p&gt;Tgoeckel: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Tgoeckel</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1227</id>
		<title>Suchen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1227"/>
				<updated>2008-05-22T13:41:51Z</updated>
		
		<summary type="html">&lt;p&gt;Tgoeckel: /* Komplexitätsanalyse */&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;
[Graph]&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>Tgoeckel</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=File:Baum_Pfad.png&amp;diff=1226</id>
		<title>File:Baum Pfad.png</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=File:Baum_Pfad.png&amp;diff=1226"/>
				<updated>2008-05-22T13:40:51Z</updated>
		
		<summary type="html">&lt;p&gt;Tgoeckel: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Tgoeckel</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1225</id>
		<title>Suchen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1225"/>
				<updated>2008-05-22T13:30:11Z</updated>
		
		<summary type="html">&lt;p&gt;Tgoeckel: /* Ungünstigster Fall von treeSearch */&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;
[Graph]&lt;br /&gt;
(Ein Baum ist ein Graph, indem es zwischen beliebigen Knoten stets geneu 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;
[Graph]&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>Tgoeckel</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=File:Balance.png&amp;diff=1224</id>
		<title>File:Balance.png</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=File:Balance.png&amp;diff=1224"/>
				<updated>2008-05-22T13:27:56Z</updated>
		
		<summary type="html">&lt;p&gt;Tgoeckel: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Tgoeckel</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1223</id>
		<title>Suchen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1223"/>
				<updated>2008-05-22T13:27:25Z</updated>
		
		<summary type="html">&lt;p&gt;Tgoeckel: &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;
[Graph]&lt;br /&gt;
(Ein Baum ist ein Graph, indem es zwischen beliebigen Knoten stets geneu 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]:&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;
[Graph]&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>Tgoeckel</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1222</id>
		<title>Suchen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1222"/>
				<updated>2008-05-22T12:12:16Z</updated>
		
		<summary type="html">&lt;p&gt;Tgoeckel: &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;
/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;
[Graph]&lt;br /&gt;
(Ein Baum ist ein Graph, indem es zwischen beliebigen Knoten stets geneu 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]: [Graph]&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;
[Graph]&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>Tgoeckel</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1197</id>
		<title>Suchen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1197"/>
				<updated>2008-05-21T09:37:41Z</updated>
		
		<summary type="html">&lt;p&gt;Tgoeckel: &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;
/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. Das 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;
&lt;br /&gt;
! key   !!  start      !! end  !! size !! center !! return  !! 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;
[Rest folgt]&lt;/div&gt;</summary>
		<author><name>Tgoeckel</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Sortieren&amp;diff=771</id>
		<title>Sortieren</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Sortieren&amp;diff=771"/>
				<updated>2008-05-12T14:42:02Z</updated>
		
		<summary type="html">&lt;p&gt;Tgoeckel: /* Charakterisierung der Effizienz von Algorithmen */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;----&lt;br /&gt;
== Laufzeitmesung in Python ==&lt;br /&gt;
&lt;br /&gt;
Verwendung der '''timeit-Bibliothek''' für die Hausaufgabe. &lt;br /&gt;
&lt;br /&gt;
* Importiere das timeit-Modul: &amp;lt;tt&amp;gt;import timeit&amp;lt;/tt&amp;gt;&lt;br /&gt;
* Teile den Algorithmus in die Initialisierungen und den Teil, dessen Geschwindigkeit gemessen werden soll. Beide Teile werden in jeweils einen (mehrzeiligen) String eingeschlossen:&lt;br /&gt;
&lt;br /&gt;
  +--------+     +----+            setup = &amp;quot;&amp;quot;&amp;quot;            prog = &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
  |  algo  | --&amp;gt; |init|                +----+                 +----+&lt;br /&gt;
  |        |     +----+                |init|                 |prog|&lt;br /&gt;
  |        |                           +----+                 +----+&lt;br /&gt;
  |        |     +----+             &amp;quot;&amp;quot;&amp;quot;                     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
  |        | --&amp;gt; |prog|            &lt;br /&gt;
  +--------+     +----+            &lt;br /&gt;
&lt;br /&gt;
* aus den beiden Strings wird ein Timeit-Objekt erzeugt: &amp;lt;tt&amp;gt;t = timeit.Timer(prog, setup)&amp;lt;/tt&amp;gt;&lt;br /&gt;
* Frage: Wie oft soll die Algorithmik wiederholt werden&lt;br /&gt;
:z.B. N = 1000&lt;br /&gt;
* Zeit in Sekunden für N Durchläufe: &amp;lt;tt&amp;gt;K = t.timeit(N)&amp;lt;/tt&amp;gt;&lt;br /&gt;
:Zeit für 1 Durchlauf: K/N&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
3.Stunde am 16.04.2008&lt;br /&gt;
&lt;br /&gt;
==Sortierverfahren==&lt;br /&gt;
&lt;br /&gt;
=== Motivation ===&lt;br /&gt;
'''Def:''' &lt;br /&gt;
Ein Sortierverfahren ist ein Algorithmus, der dazu dient, eine Liste von Elementen zu sortieren.&lt;br /&gt;
&lt;br /&gt;
'''Anwendungen'''&lt;br /&gt;
* Sortierte Daten sind häufig Vorbedingungen für Suchverfahren (Speziell für Algorithmen mit Log(N) Komplexität)&lt;br /&gt;
* Darstellung von Daten gemäß menschlicher Wahrnehmung &lt;br /&gt;
* Bemerkung: aus programmiertechnischer Anwenwendungssicht hat das Sortierproblem an Relevanz verloren da&lt;br /&gt;
** Festplatten / Hauptspeicher heute weniger limitierenden Charakter haben, so dass komplizierte, speicher-sparende Sortieralgorithmen nur noch in wenigen Fällen benötigt werden.&lt;br /&gt;
** gängige Programmiersprachen heute typenabhängige Algorithmen zur Verfügung stellen. Der Programmierer braucht deshalb sich in den meisten Fällen nicht mehr um die Implementierung von Sortieralgorithmen kümmern. In C/C++ sorgen dafür beispielsweise Methoden aus der [http://de.wikipedia.org/wiki/Standard_Template_Library STL]&lt;br /&gt;
&lt;br /&gt;
===  Vorraussetzungen/ Spielregeln ===&lt;br /&gt;
==== Mengentheoretische  Anforderungen====&lt;br /&gt;
;Definition Totale Ordnung/ Total gordnete Menge:&lt;br /&gt;
Eine Totale Ordnung / Total geordnete Menge ist eine binäre Relation    &lt;br /&gt;
&amp;lt;math&amp;gt;R \subseteq M \times M&amp;lt;/math&amp;gt; über einer Menge &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt;, die transitiv, antisymmetrisch und total ist.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; sei  dargestellt als infix Notation &amp;lt;math&amp;gt;\le &amp;lt;/math&amp;gt; dann, falls M total geordnet, gilt &lt;br /&gt;
&amp;lt;math&amp;gt; \forall a,b,c \ \epsilon M &amp;lt;/math&amp;gt; &amp;lt;br/&amp;gt;&lt;br /&gt;
(1) &amp;lt;math&amp;gt;a \le b \bigwedge b \le a \Rightarrow a=b &amp;lt;/math&amp;gt; (antisymmetrisch)&amp;lt;br/&amp;gt;&lt;br /&gt;
(2) &amp;lt;math&amp;gt;a \le b \bigwedge b \le c \Rightarrow a \le c &amp;lt;/math&amp;gt; (transitiv)&amp;lt;br/&amp;gt;&lt;br /&gt;
(3) &amp;lt;math&amp;gt;a \le b \bigvee b \le a &amp;lt;/math&amp;gt; (total) &amp;lt;br/&amp;gt;&lt;br /&gt;
Bemerkung: aus (3) folgt &amp;lt;math&amp;gt; a \le a &amp;lt;/math&amp;gt; (reflexiv) &amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
''Hab in der Wiki eine gute Seite dazu gefunden [http://de.wikipedia.org/wiki/Ordnungsrelation'' Ordnungsrelation]&lt;br /&gt;
&lt;br /&gt;
==== Datenspeicherung ====&lt;br /&gt;
a) Array (Grafik folgt noch)&lt;br /&gt;
&lt;br /&gt;
b) Vekettete Liste  &lt;br /&gt;
   Nachteil Adressierung bsp: 10 &amp;gt; 9&lt;br /&gt;
&lt;br /&gt;
==== Charakterisierung der Effizienz von Algorithmen ====&lt;br /&gt;
&lt;br /&gt;
:(a) Komplexität O(1), O(n), etc. wird in Kapitel [[Effizienz]] erklärt.&lt;br /&gt;
:(b) Zählen der notwendigen Vergleiche&lt;br /&gt;
:(c) Messen der Laufzeit mit 'timeit' (auf identischen Daten)&lt;br /&gt;
&lt;br /&gt;
'''Rekursive Zerlegung'''&lt;br /&gt;
zerlegt Ursprüngliche Probleme in kleinere Probleme und wendet sie auf die kleineren Probleme an;&lt;br /&gt;
daraufhin werden die Teilprobleme zur Lösung des Gesamtproblems verwendet. &lt;br /&gt;
'''Aufwand'''&lt;br /&gt;
für N  Eingaben, hängt ab vom Aufwand der Eingaben geringeren Umfangs ab. (Teilprobleme)&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 ----&lt;br /&gt;
----&lt;br /&gt;
4. Stunde, am 17.04.2008&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
(Fortsetzung der Stunde vom 16.04.2008)&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Mergesort ===&lt;br /&gt;
==== Algorithmus ====&lt;br /&gt;
# &amp;lt;tt&amp;gt;c ← merge(a,b)&amp;lt;/tt&amp;gt; (Siehe Mitschrift der Stunde am 16.04.2008)&lt;br /&gt;
# '''rekursiver Mergesort''':&lt;br /&gt;
&lt;br /&gt;
  mergesort(m) ← {    #m ist ein Array&lt;br /&gt;
      if |m| &amp;gt; 1    #True, wenn m mehr als 1 Element hat.&lt;br /&gt;
          a ← mergesort(m[1:&amp;lt;|m|/2])&lt;br /&gt;
          b ← mergesort(m[|m|/2-1:|m|])&lt;br /&gt;
          c ← merge(a,b)&lt;br /&gt;
          return(c)&lt;br /&gt;
      else&lt;br /&gt;
          return(m)&lt;br /&gt;
 } &lt;br /&gt;
&lt;br /&gt;
Bei der Sortierung mit Mergesort wird das Array immer in zwei Teile geteilt. → Es &lt;br /&gt;
&lt;br /&gt;
entsteht ein Binärbaum der Tiefe &amp;lt;math&amp;gt;lgN&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[Bild fehlt noch]]&lt;br /&gt;
&lt;br /&gt;
Zeitkomplexität: &amp;lt;math&amp;gt;C(N) - N \cdot lgN&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Komplexität ====&lt;br /&gt;
Komplexität: &amp;lt;math&amp;gt;C(N) = 2 \cdot \left( \frac{N}{2} \right) + N = N \cdot log_2 N &lt;br /&gt;
&lt;br /&gt;
\cdot N&amp;lt;/math&amp;gt;     (für N = &amp;lt;math&amp;gt;2^n&amp;lt;/math&amp;gt; )&lt;br /&gt;
&lt;br /&gt;
Erklärungen zur Formel: &lt;br /&gt;
* &amp;lt;math&amp;gt; C \left(\frac{N}{2}\right) &amp;lt;/math&amp;gt;: für jede Hälfte des Arrays&lt;br /&gt;
* &amp;lt;math&amp;gt; + N &amp;lt;/math&amp;gt;: für das Zusammenführen&lt;br /&gt;
&lt;br /&gt;
==== weitere Eigenschaften von MergeSort ====&lt;br /&gt;
* Mergesort ist '''stabil''', weil die Position gleicher Schlüssel im Algorithmus &lt;br /&gt;
&lt;br /&gt;
&amp;lt;tt&amp;gt;merge(a,b)&amp;lt;/tt&amp;gt; nicht verändert wird - wegen &amp;lt;tt&amp;gt;„&amp;lt;”&amp;lt;/tt&amp;gt; hat das linke &lt;br /&gt;
&lt;br /&gt;
Element Vorrang.&lt;br /&gt;
* Mergesort ist '''unempfindlich gegenüber der Reihenfolge der Eingabedaten'''. &lt;br /&gt;
&lt;br /&gt;
Grund dafür ist die vollständige Aufteilung des Ausgangsarrays in Arrays der Länge &lt;br /&gt;
&lt;br /&gt;
1.&lt;br /&gt;
* Diese Eigenschaft ist dann unerwünscht, wenn ein Teil des Arrays oder gar das &lt;br /&gt;
&lt;br /&gt;
ganze Array schon sortiert ist. Es wird nämlich in jedem Fall das ganze Array neu &lt;br /&gt;
&lt;br /&gt;
sortiert.&lt;br /&gt;
&lt;br /&gt;
=== Quicksort ===&lt;br /&gt;
&lt;br /&gt;
* Quicksort wurde in den 60er Jahren von Charles Antony Richard Hoare [http://de.wikipedia.org/wiki/C._A._R._Hoare] entwickelt. Es gibt viele Implementierungen von Quicksort, siehe  vgl. [http://de.wikipedia.org/wiki/Quicksort].&lt;br /&gt;
* Dieser Algorithmus gehört zu den &amp;quot;Teile und herrsche&amp;quot;-Algorithmen (divide-and-conquer) und ist der Standardalgorithmus für Sortieren.&lt;br /&gt;
&lt;br /&gt;
==== grundlegender Algorithmus ====&lt;br /&gt;
&lt;br /&gt;
  quicksort(l,r) ← {    #l: linke Grenze, r: rechte Grenze des Arrays&lt;br /&gt;
                       #Das Array läuft also von l bis r (a[l:r])&lt;br /&gt;
      if r &amp;gt; l&lt;br /&gt;
          i ← partition(l,r)    #i ist das Pivot-Element&lt;br /&gt;
          quicksort(l,i-1)    #quicksort auf beide Hälfte des Arrays anwenden&lt;br /&gt;
          quicksort(i+1,r)&lt;br /&gt;
  }&lt;br /&gt;
Dieser Algorithmus wird rekursiv für zwei Teilstücke links und rechts des Pivot-Elements aufgerufen. Das Pivot-Element ist nach diesem Schritt an der richtigen Position (d.h. links von der Stelle i stehen nur kleinere, rechts von i nur größere Elemente als das Pivot-Element).&lt;br /&gt;
Die Methode partition sorgt dafür, dass diese Bedingung erfüllt ist.&lt;br /&gt;
&lt;br /&gt;
==== Algorithmus für &amp;lt;tt&amp;gt;partition&amp;lt;/tt&amp;gt; ====&lt;br /&gt;
Aufgabe: Ordne &amp;lt;tt&amp;gt;a&amp;lt;/tt&amp;gt; so um, dass nach der Wahl von &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt; (Pivot-Element) &lt;br /&gt;
&lt;br /&gt;
gilt:&lt;br /&gt;
# &amp;lt;math&amp;gt;a[i]&amp;lt;/math&amp;gt; ist sortiert, d.h. dieses Element ist am endgültigen Platz.&lt;br /&gt;
# &amp;lt;math&amp;gt;\forall x \in \left\{ a \left[ l \right] , ... a \left[ i-1 \right] &lt;br /&gt;
&lt;br /&gt;
\right\} : x \leq a \left[ i \right]&amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt;\forall x \in \left\{ a \left[ i+1 \right], ... a \left[ r \right] &lt;br /&gt;
&lt;br /&gt;
\right\} : x \geq a \left[ i \right]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Abbildung fehlt noch]]  a[i] heißt Pivot-Element (p)&lt;br /&gt;
&lt;br /&gt;
  i ← partition(l,r) ← {&lt;br /&gt;
      p ← a[r]    #p: Pivot-Element. Hier wird willkürlich das rechteste Element &lt;br /&gt;
                  #   als Pivot-Element genommen.&lt;br /&gt;
      i ← l-1    #i und j sind Laufvariablen&lt;br /&gt;
      j ← r&lt;br /&gt;
      &lt;br /&gt;
      repeat&lt;br /&gt;
          repeat&lt;br /&gt;
              i ← i+1    #Finde von links den ersten Eintrag &amp;gt;= p&lt;br /&gt;
          until a[i] &amp;gt;= p&lt;br /&gt;
      &lt;br /&gt;
          repeat&lt;br /&gt;
              j ← j+1    #Finde von rechts den ersten Eintrag &amp;lt;= p&lt;br /&gt;
          until a[j] &amp;lt;= p&lt;br /&gt;
          swap(a[i], a[j])&lt;br /&gt;
      until j &amp;lt;= i    #Nachteile: p steht noch rechts&lt;br /&gt;
      swap(a[i], a[j])    #Letzter Austausch zwischen i und j muss &lt;br /&gt;
                              #zurückgenommen werden&lt;br /&gt;
      swap(a[i], a[r])&lt;br /&gt;
      return(i)&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
[[Abbildung fehlt noch]]&lt;br /&gt;
&lt;br /&gt;
[[Beispiel fehlt noch]]&lt;br /&gt;
&lt;br /&gt;
Bemerkungen zur gegebenen Implementierung:&lt;br /&gt;
#  Sie benötigt ein Dummy-Minimalelement.&lt;br /&gt;
#* Dieses Element ist durch zusätzliche &amp;lt;tt&amp;gt;if&amp;lt;/tt&amp;gt;-Abfrage vermeidbar, aber die &lt;br /&gt;
&lt;br /&gt;
&amp;lt;tt&amp;gt;if&amp;lt;/tt&amp;gt;-Abfrage erhöht die Komplexität des Algorithmus (schlechte Performanz).&lt;br /&gt;
# Sie ist ineffizient für (weitgehend) sortierte Arrays, da sich folgende &lt;br /&gt;
&lt;br /&gt;
Aufteilung für die Partitionen ergibt:&lt;br /&gt;
# Erste Partition: &amp;lt;tt&amp;gt;[l,i-1]&amp;lt;/tt&amp;gt;, zweite Partition: &amp;lt;tt&amp;gt;[i+1,r]&amp;lt;/tt&amp;gt;&lt;br /&gt;
# Die erste Partition umfasst &amp;lt;tt&amp;gt;N-1&amp;lt;/tt&amp;gt; Elemente&lt;br /&gt;
# Die zweite Partition ist leer (bzw. sie existiert nicht), weil das Pivot-Element &amp;lt;tt&amp;gt;p = r&amp;lt;/tt&amp;gt; gewählt wurde. Das Array wird also elementweise abgearbeitet.&lt;br /&gt;
# → &amp;lt;tt&amp;gt;N&amp;lt;/tt&amp;gt; einzelne Aufrufe ⇒ Zeitkomplexität: &amp;lt;math&amp;gt;\approx &lt;br /&gt;
&lt;br /&gt;
\frac{N^2}{2}&amp;lt;/math&amp;gt; (siehe Berechnung vom 16.04.2008)&lt;br /&gt;
# Für identische Schlüssel sollten beide Laufvariablen stehen bleiben.&lt;br /&gt;
# Bei der gegebenen Implementierung tauscht auch gleiche Elemente aus.&lt;br /&gt;
# Für identische Schlüssel können die Abbruchbedingungen verbessert werden (siehe &lt;br /&gt;
&lt;br /&gt;
Sedgewick).&lt;br /&gt;
&lt;br /&gt;
==== Komplexität ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C(N) = (N+1) + \frac{1}{N} \sum_{m=1}^{N} \left[ C(k-1) + C(N-k) &lt;br /&gt;
&lt;br /&gt;
\right]&amp;lt;/math&amp;gt; für &amp;lt;math&amp;gt; N&amp;gt;1;\, C_1 = C_0 =0 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Anmerkungen zur Formel:&lt;br /&gt;
# &amp;lt;math&amp;gt;(N+1)&amp;lt;/math&amp;gt;: Vergleiche für jeden Aufruf&lt;br /&gt;
# &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt;: Teilungspunkt&lt;br /&gt;
&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{1}{N} \sum_{m=1}^{N} \left[ C(k-1) + C(N-k) \right] = 2 \frac{1}{N} &lt;br /&gt;
&lt;br /&gt;
\sum_{k=1}^{N} C(k-1) &lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
C(N) = (N+1) + \frac{1}{N} \sum_{m=1}^{N} \left[ C(k-1) + C(N-k) \right]&lt;br /&gt;
\overset{\cdot N}{\longleftrightarrow} &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
N \cdot C(N) = N \left[ (N+1) + \frac{2}{N} \sum_{k=1}^{N} C(k-1) \right] &lt;br /&gt;
\overset{-\, (N-1) \cdot C(N-1)}{\longleftrightarrow} &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
N \cdot C(N) - (N-1) \cdot C(N-1) = N(N+1) - (N-1) \cdot N + 2 \sum_{k=1}^{N} &lt;br /&gt;
&lt;br /&gt;
C(k-1) - 2 \sum_{k=1}^{N} C(k-1) &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
= N(N+1) - (N-1) \cdot N + 2 \cdot C(N-1) \longleftrightarrow &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
N \cdot C(N) = N(N+1) - (N-1) \cdot N + 2 \cdot C(N-1) + (N-1) + (N-1) \cdot &lt;br /&gt;
&lt;br /&gt;
C(N-1) = 2N + (N+1) \cdot C(N-1) \overset{/N(N+1)}{\longleftrightarrow} &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{C(N)}{N+1} = \frac{C(N-1)}{N} + \frac{2}{N+1} &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; &lt;br /&gt;
= \frac{C(N-2)}{N-1} + \frac{2}{N} + \frac{2}{N+1} &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
= \frac{C(N-3)}{N-2} + \frac{2}{N-1} + \frac{2}{N} + \frac{2}{N+1} &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
= ... = &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
= \frac{C(2)}{3} + 2 \sum_{k=3}^{N} \frac{1}{k+1} \approx 2 \sum_{k=3}^{N} &lt;br /&gt;
&lt;br /&gt;
\frac{1}{k+1} \approx 2 \int_1^N \frac{1}{k} dk = 2 \cdot ln N &lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
Für sehr große N gilt:&lt;br /&gt;
&amp;lt;math&amp;gt;\approx 2 \sum_{k=1}^{N} \frac{1}{k}&amp;lt;/math&amp;gt; beziehungsweise &amp;lt;math&amp;gt; \geq 2 &lt;br /&gt;
&lt;br /&gt;
\sum_{k=1}^{N} \frac{1}{k}&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
Mittlere Komplexität:&lt;br /&gt;
&amp;lt;math&amp;gt;C(N) = 2(N+1) \cdot lnN \approx 2N \cdot lnN &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
==== Verbesserungen des Algorithmus ====&lt;br /&gt;
# Eine Verbesserung beseitigt die Rekursion durch Verwendung eines Stacks.&lt;br /&gt;
# &amp;quot;r&amp;quot; wird immer kleiner → Der rekursive Aufruf lohnt sich nicht mehr. → Explizites Sortieren einsetzen.&lt;br /&gt;
# Das Pivot-Element könnte geschickter gewählt werden: Median.&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
[[Special:Contributions/147.142.207.188|147.142.207.188]] 19:31, 23 April 2008 (UTC)&lt;/div&gt;</summary>
		<author><name>Tgoeckel</name></author>	</entry>

	</feed>