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

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=File:Abild.png&amp;diff=2348</id>
		<title>File:Abild.png</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=File:Abild.png&amp;diff=2348"/>
				<updated>2008-07-14T13:18:53Z</updated>
		
		<summary type="html">&lt;p&gt;Nata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Nata</name></author>	</entry>

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

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=File:ABild.png&amp;diff=2157</id>
		<title>File:ABild.png</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=File:ABild.png&amp;diff=2157"/>
				<updated>2008-07-06T20:53:00Z</updated>
		
		<summary type="html">&lt;p&gt;Nata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Nata</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=File:Bild_a.png&amp;diff=2156</id>
		<title>File:Bild a.png</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=File:Bild_a.png&amp;diff=2156"/>
				<updated>2008-07-06T20:51:19Z</updated>
		
		<summary type="html">&lt;p&gt;Nata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Nata</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=File:Bild.png&amp;diff=2155</id>
		<title>File:Bild.png</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=File:Bild.png&amp;diff=2155"/>
				<updated>2008-07-06T20:46:34Z</updated>
		
		<summary type="html">&lt;p&gt;Nata: uploaded a new version of &amp;quot;Image:Bild.png&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Nata</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=File:Bild.png&amp;diff=2154</id>
		<title>File:Bild.png</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=File:Bild.png&amp;diff=2154"/>
				<updated>2008-07-06T20:38:29Z</updated>
		
		<summary type="html">&lt;p&gt;Nata: uploaded a new version of &amp;quot;Image:Bild.png&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Nata</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=File:Bild.png&amp;diff=2153</id>
		<title>File:Bild.png</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=File:Bild.png&amp;diff=2153"/>
				<updated>2008-07-06T20:12:55Z</updated>
		
		<summary type="html">&lt;p&gt;Nata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Nata</name></author>	</entry>

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

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

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=File:Baum_voll.png&amp;diff=2149</id>
		<title>File:Baum voll.png</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=File:Baum_voll.png&amp;diff=2149"/>
				<updated>2008-07-06T15:27:08Z</updated>
		
		<summary type="html">&lt;p&gt;Nata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Nata</name></author>	</entry>

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

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

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=File:Sentinel.png&amp;diff=2146</id>
		<title>File:Sentinel.png</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=File:Sentinel.png&amp;diff=2146"/>
				<updated>2008-07-06T15:21:48Z</updated>
		
		<summary type="html">&lt;p&gt;Nata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Nata</name></author>	</entry>

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

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1521</id>
		<title>Suchen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1521"/>
				<updated>2008-05-29T01:27:00Z</updated>
		
		<summary type="html">&lt;p&gt;Nata: /* Balance eines Baumes zu  definieren: */&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 = sequentialSearch(a, key) &lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq &amp;lt;/math&amp;gt; foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def sequentialSearch(a, key):&lt;br /&gt;
    for i in range(len(a)):&lt;br /&gt;
        if a[i] == key:  # bzw. allgemeiner a[i].key == key &lt;br /&gt;
            return i&lt;br /&gt;
    return -1&lt;br /&gt;
&lt;br /&gt;
Wir wollen jetzt die Komplexität dieses Algorithmus bestimmen, wobei die Problemgröße durch &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt; gegeben ist. &lt;br /&gt;
&lt;br /&gt;
Dabei nimmt man an, dass der innerste Vergleich (a[i] == key) jeweils &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt; ist (diese Annahme könnte verletzt sein, wenn der Vergleichsoperator überladen ist und dadurch eine höhere Komplexität hat). Dieser Vergleich wird in der for-Schleife jeweils N-mal durchgeführt (&amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt;), so dass man nach der Verschachtelungsregel eine gesamte Komplexität von &amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt; erhält.&lt;br /&gt;
&lt;br /&gt;
Der Name ''lineare'' Suche rührt von diesem linearen Anwachsen der Komplexität mit der Arraygröße her.&lt;br /&gt;
&lt;br /&gt;
==Binäre Suche==&lt;br /&gt;
&lt;br /&gt;
Wie wir weiter unten zeigen werden, gestattet es diese Suchmethode, die Gesamtdauer der Suche in großen Datensätzen beträchtlich zu verringern. Die Methode beruht auf dem [http://de.wikipedia.org/wiki/Divide_and_Conquer Divide and Conquer-Prinzip], wobei die Suche in jedem Schritt rekursiv auf eine Hälfte des Datensatzes eingeschränkt wird. Weitere Details zur Methode sind [http://de.wikipedia.org/wiki/Bin%C3%A4re_Suche hier] zu finden. &lt;br /&gt;
&lt;br /&gt;
Die Methode ist nur dann anwendbar beziehungsweise effektiv, wenn folgendes gilt:&lt;br /&gt;
&lt;br /&gt;
# Auf der Eigenschaft der Daten, die zur Suche verwendet wird, ist eine Ordnung im Sinne von &amp;lt; oder &amp;gt; definiert.&lt;br /&gt;
# Wir wollen uns auf Datensätze beschränken, die schon fertig aufgebaut sind, in die also keine neuen Elemente mehr eingefügt werden, wenn man mit dem Suchen beginnt. Ist dies nicht der Fall, so ist unter Umständen die Implementierung über einen [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum Binärbaum] (siehe auch weiter unten) geschickter. &lt;br /&gt;
&lt;br /&gt;
Der folgende Algorithmus zeigt eine beispielhafte Implementierung der Methode (wir setzen wieder &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt;):&lt;br /&gt;
&lt;br /&gt;
 a = [...,...]     # array&lt;br /&gt;
 a.sort()   # sortiere über Ordnung des Schlüssels&lt;br /&gt;
 foundIndex = binSearch(a, key, 0, len(a))  # (Array, Schlüssel, von wo bis wo suchen im Array)&lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq&amp;lt;/math&amp;gt;  foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def binSearch(a, key, start, end):  # start ist 1. Index, end ist letzter Index + 1&lt;br /&gt;
    size = end - start   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if size &amp;lt;= 0:   # Bereich leer?  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return -1   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    center = (start + end)/2   # Integer Division, Ergebnis wird abgerundet, wichtig für ganzzahlige Indizes &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if a[center] == key:  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return center  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    elif a[center] &amp;lt; key:  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return binSearch(a, key, center + 1, end)  # Rekursion&lt;br /&gt;
    else:&lt;br /&gt;
        return binSearch(a, key, start + 1, center)  # Rekursion&lt;br /&gt;
&lt;br /&gt;
Zur Berechnung der Komplexität dieses Algorithmus vernachlässigen wir zunächst den Aufwand, den die Sortierung weiter oben verursacht. Dieser Schritt mag oder mag nicht zulässig sein.&lt;br /&gt;
&lt;br /&gt;
Nach der Sequenzregel haben auch alle &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;.  Es bleibt die Komplexität der Rekursion zu berechnen. Die gesamte Komplexität des Algorithmus (jetzt als Funktion f bezeichnet) setzt sich zusammen aus den oben erwähnten &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen sowie der Rekursion und ist &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f(N) = \mathcal{O}(1) + f(N/2) = \mathcal{O}(1) + \mathcal{O}(1) + f(N/4) = ... = \underbrace{\mathcal{O}(1) + ... + \mathcal{O}(1) + \underbrace{f(0)}_{\mathcal{O}(1)\, \rightarrow \,\mathrm{size-Abfrage}}}_{n+1 \,\mathrm{Terme}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Falls jetzt gilt &amp;lt;math&amp;gt; N = 2^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \rightarrow f(N) = \mathcal{O}(1) \cdot \mathcal{O}(n+1) = \mathcal{O}(n) = \mathcal{O}(\lg N) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Für große Datenmengen ist die ''binäre Suche'' also weit effizienter als die ''lineare Suche''. Verdoppelt sich beispielsweise die zu durchsuchende Datenmenge, so verdoppelt sich der Aufwand für die ''sequentielle Suche'' - bei der ''binären Suche'' hingegen benötigt man lediglich eine zusätzliche Vergleichsoperation. &lt;br /&gt;
&lt;br /&gt;
Für kleine Daten (&amp;lt;math&amp;gt; N = 4,\, 5 &amp;lt;/math&amp;gt;) ist die ''sequentielle Suche'' jedoch schneller als die ''binäre Suche'', da hier die rekursiven Funktionsaufrufe teurer als das Mehr an Vergleichen sind. Ein anderer ungünstiger Fall ist gegeben, wenn nur sehr wenige Suchanfragen erfolgen (weniger als &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; viele). Dann wird der Aufwand durch das Sortieren des Arrays dominiert, ist also &amp;lt;math&amp;gt;\mathcal{O}(N \lg N) &amp;lt;/math&amp;gt;. Auch dann ist sequentielle Suche vorzuziehen.&lt;br /&gt;
&lt;br /&gt;
Eine relativ einfache Möglichkeit, die ''binäre Suche'' zu verbessern, ist die sogenannte ''Interpolationssuche''. Hierbei wird die neue Position für die Suche, also die Mitte des Arrays, durch eine Schätzung ersetzt, die angibt, wo sich der Schlüssel innerhalb des Arrays befinden könnte. Bei der Suche in einem Telefonbuch nach dem Namen Zebra würde man ja auch nicht in der Mitte anfangen. Näheres hierzu im Buch von ''Sedgewick''.&lt;br /&gt;
&lt;br /&gt;
Um sich den Algorithmus der ''binären Suche'' klar zu machen, ist es instruktiv, sich die folgende Tabelle genauer anzusehen, die die sukzessive Belegung der Variablen bei verschiedenen Anfragen beschreibt. Die Testfälle wurden nach dem Prinzip des ''domain partitioning'' gewählt. Das zugehörige Array hat die Einträge&lt;br /&gt;
&lt;br /&gt;
 a = [2, 3, 4, 5, 6]&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align:center&amp;quot; border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;5&amp;quot; cellspacing=&amp;quot;0&amp;quot; &lt;br /&gt;
! gesuchter key   !!  start      !! end  !! size !! center !! return &amp;lt;br/&amp;gt; (-1 oder index)  !! Kommentare  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 4     ||0            || 5    ||  5   || 2   ||  2         || gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 2     || 0           || 5    ||  5   || 2   ||            || linker Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||           &lt;br /&gt;
|-&lt;br /&gt;
|       ||  0          || 1    ||  1   || 0   ||  0         || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 1     ||0            || 5    ||  5   || 2   ||            || links außerhalb&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 1    ||  1   || 0   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 0    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 6     ||0            || 5    ||  5   || 2   ||            || rechter Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    || 2    || 4   || 4          || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 5     ||0            || 5    ||  5   || 2   ||            || typischer Fall&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||3            || 5    ||  2   || 4   ||            ||  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       || 3           || 4    ||  1   || 3   || 3          || gefunden&lt;br /&gt;
|- &lt;br /&gt;
| 7     ||0            || 5    ||  5   || 2   ||            || rechts außerhalb        &lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    ||  2   || 4   ||            ||&lt;br /&gt;
|-&lt;br /&gt;
|       ||5            || 5    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Suche in einem Binärbaum ==&lt;br /&gt;
&lt;br /&gt;
Eine kurze Einführung in Binärbäume findet man [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum hier].&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum.png|text-top|300x300px|Zur Illustration von Bäumen]]&lt;br /&gt;
&lt;br /&gt;
Bäume sind zweidimensional verkettete Strukturen. Sie gehören zu den fundamentalen Datenstrukturen in der Informatik. Da man in Bäumen nicht nur Daten speichern kann, sondern auch relevante Beziehungen der Daten untereinander, festgelegt über eine Ordnung auf der vergleichenden Dateneigenschaft (''Schlüssel''), eignen sich Bäume also insbesondere, um gesuchte Daten schnell wieder auffinden zu können.&lt;br /&gt;
&lt;br /&gt;
Ein ''Binärbaum'' wie oben skizziert besteht aus einer Menge von ''Knoten'', die untereinander durch ''Kanten'' verbunden sind. Jeder Knoten hat einen linken und einen rechten Unterbaum, der auch leer sein kann (in Python ließe sich dies mit ''None'' implementieren). Führt eine Kante von Knoten A zu Knoten B, so heißt A Vater von B und B Kind von A. Es gibt genau einen Knoten ohne Vater, den man ''Wurzel'' nennt. Knoten ohne Kinder heißen ''Blätter''.&lt;br /&gt;
&lt;br /&gt;
Ein ''Suchbaum'' hat zusätzlich die Eigenschaft, dass die Schlüssel jedes Knotens sortiert sind. Alle Schlüssel im linken Unterbaum sind kleiner, alle Schlüssel im rechten Unterbaum sind größer als ihr Vater. Wir wollen hierbei annehmen, dass jeder Schlüssel pro Datensatz nur einmal vorkommt, da sich sonst die &amp;gt;- oder &amp;lt;-Relation nicht mehr strikt erfüllen ließe.&lt;br /&gt;
&lt;br /&gt;
Um in einem Baum suchen zu können, wollen wir von zwei Annahmen ausgehen:&lt;br /&gt;
# Einfügen und Suchen im Baum wechseln sich ab. (Wenn das Suchen erst beginnt, nachdem alle Einfügungen erfolgt sind, wäre ein dynamisches Array mit binärer Suche wie oben wesentlich einfacher.)&lt;br /&gt;
# Der Schlüssel, der die Anordnung bestimmt, kennt eine [http://de.wikipedia.org/wiki/Ordnungsrelation Ordnung] (&amp;lt;-Relation oder &amp;gt;-Relation).&lt;br /&gt;
&lt;br /&gt;
Der folgende Python-Code zeigt beispielhaft, wie man in einem Suchbaum suchen könnte. Der Konstruktor für einen Knoten des Suchbaums ließe sich zum Beispiel so implementieren:&lt;br /&gt;
 &lt;br /&gt;
 class Node:&lt;br /&gt;
     def __init__(self, key):&lt;br /&gt;
         self.key = key&lt;br /&gt;
         self.left = self.right = None&lt;br /&gt;
 &lt;br /&gt;
 root = ...    # Wurzel des Suchbaums&lt;br /&gt;
 nodeFound = treeSearch(root, key)   # None, falls nichts gefunden&lt;br /&gt;
 &lt;br /&gt;
 def treeSearch(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return None&lt;br /&gt;
     elif node.key == key:&lt;br /&gt;
         return node&lt;br /&gt;
     elif key &amp;lt; node.key:&lt;br /&gt;
         return treeSearch(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         return treeSearch(node.right, key)&lt;br /&gt;
&lt;br /&gt;
Daraus resultiert der folgende Suchalgorithmus:&lt;br /&gt;
&lt;br /&gt;
 def treeSort(node,array):     # dynamisches Array als 2. Argument&lt;br /&gt;
     if node is None:     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
         return None:&lt;br /&gt;
     treeSort(node.left, array)     # rekursiv&lt;br /&gt;
     array.append(node.key)     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
     treeSort(node.right, array)     # rekursiv&lt;br /&gt;
&lt;br /&gt;
Komplexität:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 f(N)=\mathcal{O}(1)+f(N_\mathrm{left})+f(N_\mathrm{right})=\mathcal{O}(1)+\mathcal{O}(1)+f(N_\mathrm{leftleft})+f(N_\mathrm{leftright})+\mathcal{O}(1)+f(N_\mathrm{rightleft})&lt;br /&gt;
 +f(N_\mathrm{leftright})=N\ast\mathcal{O}(1)=\mathcal{O}(N)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sortier-Pseudocode:&lt;br /&gt;
&lt;br /&gt;
 Sortieren:&lt;br /&gt;
     (Array) a     # unsortiert&lt;br /&gt;
     (tree) t     # zunächst leer&lt;br /&gt;
 (dynamisches Array) r     # später sortiert&lt;br /&gt;
 for e in a:&lt;br /&gt;
     t = treeInsert(t, e)&lt;br /&gt;
 treeSort(t, r)&lt;br /&gt;
&lt;br /&gt;
== Insert ==&lt;br /&gt;
&lt;br /&gt;
Was passiert wenn der key (Schlüssel) schon vorhanden ist?&lt;br /&gt;
* error (exception)&lt;br /&gt;
* nichts einfügen&lt;br /&gt;
* nichts einfügen aber einen boolean return zurückgeben (einfügen=true, false)&lt;br /&gt;
* nochmals eingefügt (z.B. in der Node Klasse)&lt;br /&gt;
&lt;br /&gt;
Wobei die ersten 3 Punkte zur Mengensemantik gehören und der letzte eine Multimenge ist.&lt;br /&gt;
&lt;br /&gt;
Algorithmus im zweiten Fall (nichts einfügen):&lt;br /&gt;
&lt;br /&gt;
 def treeInsert(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return Node(key)     # Alternative Schreibweise: node = Node(key)&lt;br /&gt;
     if node.key == key:      # und dann:&lt;br /&gt;
         return node          # pass&lt;br /&gt;
     elif key &amp;lt; node.key:     # links im Baum&lt;br /&gt;
         node.left = treeInsert(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         node.right = treeInsert(node.right, key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Remove ==&lt;br /&gt;
Fälle:&lt;br /&gt;
# key (bzw. Knoten der key enthält) ist ein Blatt =&amp;gt; einfach löschen&lt;br /&gt;
# node hat &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; linken Unterbaum oder &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; rechten Unterbaum =&amp;gt; durch Unterbaum ersetzen&lt;br /&gt;
# node hat beide Unterbäume:&lt;br /&gt;
#* Suche Vorgänger: max k &amp;lt; key (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; keys); Vorgänger ist immer ein Fall 1 oder Fall 2&lt;br /&gt;
#*:=&amp;gt; ersetze node durch Vorgänger und entferne Vorgänger&lt;br /&gt;
&lt;br /&gt;
 def treePredecessor(node):     # wird nur bei Fall 3 aufgerufen&lt;br /&gt;
     node = node.left&lt;br /&gt;
     while node.right is not None:&lt;br /&gt;
         node = node.right&lt;br /&gt;
     return node&lt;br /&gt;
 &lt;br /&gt;
 def treeRemove(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return node&lt;br /&gt;
     if key &amp;lt; node.key:&lt;br /&gt;
         node.left = treeRemove(node.left, key)&lt;br /&gt;
     elif key &amp;gt; node.key:&lt;br /&gt;
         node.right = treeRemove(node.right, key)&lt;br /&gt;
     else:&lt;br /&gt;
         if node.left is None and node.right is None:     # Fall 1&lt;br /&gt;
             node = None&lt;br /&gt;
         elif node.left is None:     # Fall 2&lt;br /&gt;
             node = node.right       # +&lt;br /&gt;
         elif node.right is None:    # Fall 2&lt;br /&gt;
             node = node.left&lt;br /&gt;
         else:&lt;br /&gt;
             pred = treePredecessor(node)&lt;br /&gt;
             node.key = pred.key&lt;br /&gt;
             node.left = treeRemove(node.left, pred.key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Komplexitätsanalyse ==&lt;br /&gt;
&lt;br /&gt;
* Pfad (Zwischen node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;): &lt;br /&gt;
**Folge von Knoten (node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt;,...,node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt;), sodass:&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;ki&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;ki+1&amp;lt;/sub&amp;gt; haben eine gemeinsame Kante.&lt;br /&gt;
[[Image:Baum_Pfad.png]]&lt;br /&gt;
(Ein Baum ist ein Graph, indem es zwischen beliebigen Knoten stets genau einen Pfad gibt.)&lt;br /&gt;
&lt;br /&gt;
* Länge eines Pfades: Anzahl der Kanten = Anzahl der Knoten - 1&lt;br /&gt;
* Tiefe eines Knotens: Pfadlänge vom Knoten zur Wurzel des Baumes.&lt;br /&gt;
* Tiefe des Baumes: maximale Tiefe eines Knotens&lt;br /&gt;
* Balance eines Baumes: maximale Tiefe(k) - minimale Tiefe(k) (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; Blätter)&lt;br /&gt;
&lt;br /&gt;
== Ungünstigster Fall von treeSearch ==&lt;br /&gt;
&lt;br /&gt;
Komplexität von treeSearch = Länge des Pfades zum Knoten wo &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; gefunden wird, oder erkannt wird, dass &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; nicht im Baum ist.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; wird nicht gefunden, aber für diese Entscheidung muss der Längste Pfad vollständig durchlaufen werden.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(T)&amp;lt;/math&amp;gt; wo T = Tiefe des Baumes&lt;br /&gt;
*: [1,2,3,4,5]:&amp;lt;br /&amp;gt; [[Image:Balance.png]]&lt;br /&gt;
*: Tiefe T = 4, Balance = 4&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; wo N = Anzahl der Elemente.&lt;br /&gt;
&lt;br /&gt;
== Aufgabe ==&lt;br /&gt;
&lt;br /&gt;
Minimiere Balance (erzeuge balancierten Baum):&lt;br /&gt;
# Einfügen in geschickter Reihenfolge (siehe Übungsaufgabe)&lt;br /&gt;
# Selbstbalancierter Baum:&lt;br /&gt;
#* Überprüfen der Balance nach jedem Einfügen&lt;br /&gt;
#* Umstrukturieren des Baumes, falls Balance &amp;gt; 1 (Suchbaum-Bedingung muss erhalten bleiben)&lt;br /&gt;
#* AVL-Bäume (älteste Variante)&lt;br /&gt;
#* Rot-Schwarz-Bäume (verbreiteste Variante)&lt;br /&gt;
#* Treaps (flexibelste Variante, siehe Übung)&lt;br /&gt;
#* Splay trees&lt;br /&gt;
#* Andersson Trees (einfachste Variante)&lt;br /&gt;
(#* Skip Lists (schnellste Variante, aber kein Binärbaum))&lt;br /&gt;
&lt;br /&gt;
== Umstrukturieren, so dass Suchbaumbedingung erhalten bleibt: ==&lt;br /&gt;
&lt;br /&gt;
Rotation: elementare Umstrukturierungen&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum_Rotation.png]]&lt;br /&gt;
&lt;br /&gt;
 def rotateRight(node):&lt;br /&gt;
     newRoot = node.left&lt;br /&gt;
     node.left = newRoot.right&lt;br /&gt;
     newRoot.right = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
 def rotateLeft(node):&lt;br /&gt;
     newRoot = node.right&lt;br /&gt;
     node.right = newRoot.left&lt;br /&gt;
     newRoot.left = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
== Balance eines Suchbaumes ==&lt;br /&gt;
&lt;br /&gt;
===Balance eines Baumes zu  definieren:===&lt;br /&gt;
[[Image:Abbildung1.jpg|200px|right]]&lt;br /&gt;
*betrachte &amp;lt;tt&amp;gt;None&amp;lt;/tt&amp;gt; als zusätzlichen Knoten : '''sentinel'''   ([[englische Sprache|engl.]] für ''Wächter'')&lt;br /&gt;
*oder defeniere speziellen sentinel-Knoten&lt;br /&gt;
*RS - Pfades : von &amp;lt;tt&amp;gt;root&amp;lt;/tt&amp;gt; &amp;amp;rarr; ''sentinel''&lt;br /&gt;
*Länge eines Pfades ''P''      '''''|P|'''''&lt;br /&gt;
* &amp;lt;u&amp;gt;die Balance eines Baumes zu defenieren:&amp;lt;/u&amp;gt;&lt;br /&gt;
: '''''B = max P '''''&amp;lt;math&amp;gt; \in &amp;lt;/math&amp;gt; '''''{RS}    |P| - min |P|'''''&lt;br /&gt;
:'''''{RS}'''''  Menge aller RS-Pfade&lt;br /&gt;
;vollständiger Baum:  Balance &amp;lt;math&amp;gt;~=0&amp;lt;/math&amp;gt;&lt;br /&gt;
:Alle Knoten(außer Blättern) haben 2 Kinder.&lt;br /&gt;
;perfect balancierter Baum:  Balance  &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 1&lt;br /&gt;
'''alternative Defenition für perfect balancierte Bäume'''&lt;br /&gt;
:Für jeden Teilbaum gilt es: rechtes und linkes Kind (für jeden Knoten) sind auch wieder perfect balancierte Bäume und ihre Höhe unterscheidet sich höchstens um '''1'''.&lt;br /&gt;
&lt;br /&gt;
===Größe===&lt;br /&gt;
[[Image:baum-v.png|700px|right]]&lt;br /&gt;
====Größe des vollständigen Baumes====&lt;br /&gt;
Ebene K hat 2 &amp;lt;sup&amp;gt;k&amp;lt;/sup&amp;gt; Knoten.&lt;br /&gt;
Falls Tiefe = d , dann N = 2&amp;lt;sup&amp;gt;0&amp;lt;/sup&amp;gt; + 2&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;.....+ 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; = 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1&lt;br /&gt;
&lt;br /&gt;
====Größe des perfect balancierten Baumes====&lt;br /&gt;
Die Tiefe d, kann nicht besser sein als vollständiger Baum&lt;br /&gt;
:'''=&amp;gt;'''N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2 &amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
;schlechteste perfect balancierter Baum : ist ein vollständiger der Tiefe  d - 1 + 1, N &amp;lt;math&amp;gt; \ge&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:'''=&amp;gt;'''  2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1)   &amp;lt;math&amp;gt;~&amp;lt;&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt;)&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
d &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt;  log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; (d + 1)&lt;br /&gt;
&lt;br /&gt;
*Ergibt die Komplexität der Suche im schlechtesten Fall: Anzahl der Vergleiche pro Knoten( = 2 bzw. = 3)&amp;lt;math&amp;gt;\ast&amp;lt;/math&amp;gt;Anzahl der Knoten&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow ~f(N)\le 2d \le 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
*'''perfekt balancierter Baum '''=&amp;gt; AVL-Baum &lt;br /&gt;
*'''balancierter Baum''':&lt;br /&gt;
::&amp;lt;math&amp;gt;\forall  N:d(N)\le c \ast d (N)&amp;lt;/math&amp;gt; und  &amp;lt;math&amp;gt;1 \le c &amp;lt; \mathcal {1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:: d ist die Tiefe von perfekt balancierten Baum&lt;br /&gt;
&lt;br /&gt;
:''Komplexität der Suche'':&amp;lt;math&amp;gt; ~f(N)\le  c\ast 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
:algorithmisch einfacher als perfekt balancierter Baum, aber fast genauso schnell&lt;br /&gt;
&lt;br /&gt;
====Selbst-balancierter Baum====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  def insertTree(node,key):&lt;br /&gt;
    if node is sentinel:       #(None = sentinel)&lt;br /&gt;
        return Node(key)       &lt;br /&gt;
    if node.key == key:&lt;br /&gt;
        return Node&lt;br /&gt;
    if key &amp;lt; node.key:&lt;br /&gt;
        Node = insertTree(node.left, key)&lt;br /&gt;
    else:&lt;br /&gt;
        node.right = insertTree(node.right, key)&lt;br /&gt;
   #optimiere Balance:&lt;br /&gt;
        return node&lt;br /&gt;
&lt;br /&gt;
===Anderson-Bäume===&lt;br /&gt;
&lt;br /&gt;
  class Node:&lt;br /&gt;
    def__init__(self, key):&lt;br /&gt;
        self.key = key&lt;br /&gt;
        self.left = selft.right = None&lt;br /&gt;
        #einfügen:&lt;br /&gt;
        self.level = 1&lt;br /&gt;
&lt;br /&gt;
:''level'' : kodiert Abstand von sentinel&lt;br /&gt;
====Regeln====&lt;br /&gt;
Es gibt vertikale Kanten(parent.level == child.level + 1 ) und horizontale Kanten(parent.level == child.level)&lt;br /&gt;
:*verfeinerter Regel :  ''level kodiert reduzierten Abstand von Setinel''  (d.b. horizontale Kanten werden nicht gezählt)&lt;br /&gt;
::(die nächste zwei Regeln sichern die Balance):&lt;br /&gt;
::*alle RS-Pfade haben die gleiche reduzierte Länge oder ''root'' hat bei allen Pfaden das gleiche Level&lt;br /&gt;
::*kein Pfad hat 2 aufeinanderer folgende horizontale Kanten&lt;br /&gt;
:*nur Kanten zum rechten Kind dürfen horizontal sein&lt;br /&gt;
:*die reduzierte Höhe jedes Blatts ist '''hr=1'''&lt;br /&gt;
&lt;br /&gt;
====Beweis====&lt;br /&gt;
Vereinfachung des Algorithmus&lt;br /&gt;
:'''Satz''':ein Anderson-Baum ist balanciert.&lt;br /&gt;
*1. Sei '''hr'''- die reduzierte Höhe&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; jeder Teilbaum enthält mindestens &amp;lt;math&amp;gt;N\ge 2^{hr} -1 &amp;lt;/math&amp;gt;  Knoten&lt;br /&gt;
&lt;br /&gt;
::a). Blätter : reduzierte Höhe 1 =&amp;gt; &amp;lt;math&amp;gt; N\ge 2^{1} - 1 = 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
::b). inneren Knoten: jeder Unterbaum hat mindestens reduzierte Höhe &amp;lt;math&amp;gt; ~hr - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt;  jeder Unterbaum hat mindestens &amp;lt;math&amp;gt; ~2^{hr} -1 &amp;lt;/math&amp;gt; Knoten&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt;N \ge 2 (2^{hr-1} -1)+1 = 2^w - 2 + 1 = 2^{hr} -1&amp;lt;/math&amp;gt;&lt;br /&gt;
::::alle RS-Pfade heben gleiche Länge&lt;br /&gt;
[[Image:Ander.jpg|450px|right]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
*2. Kein Pfad hat 2 aufeinanderfolgende horizontal Kanten&lt;br /&gt;
:::&amp;lt;math&amp;gt;\Rightarrow  d \le 2 hr + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
*3. zusammen:&lt;br /&gt;
:&amp;lt;math&amp;gt;N \ge 2^{\frac {d}{2}} - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;log_{2}{ N}+1\ge log 2^{\frac {d}{2}} - 1 \ge \frac {d}{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; ~d &amp;lt; 2 log_{2}{(N + 1)}&amp;lt;/math&amp;gt;     - balancierter Baum&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
:'''Suchzeit''' : &amp;lt;math&amp;gt; ~ f(N) = \mathcal{O}(log{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Wie erreicht man die Balance===&lt;br /&gt;
====Rotation====&lt;br /&gt;
[[Image:Abbildung4.jpg|text-top|500px|Illustration von Rotation]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
   def rotateRight (node):&lt;br /&gt;
      root = node.left&lt;br /&gt;
      node.left = node.right&lt;br /&gt;
      root.right = node&lt;br /&gt;
      return root&lt;br /&gt;
   def rotateLeft(node):&lt;br /&gt;
      root = node.right&lt;br /&gt;
      node.right = node.left&lt;br /&gt;
      root.left = node&lt;br /&gt;
      return root&lt;br /&gt;
&lt;br /&gt;
====Optimierung der Balance====&lt;br /&gt;
   &lt;br /&gt;
&lt;br /&gt;
       if node.left is not sentinel and node.level==node.left.level:&lt;br /&gt;
            node &amp;gt; rotateRight(node)&lt;br /&gt;
       if node.right is not sentinel and node.right.right is  not sentinel and node.level==rotate.right.right.level:&lt;br /&gt;
            node = rotateLeft(node)&lt;br /&gt;
            node.level += 1&lt;br /&gt;
[[Image:rotate.jpg|text-top]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
[[Image:liste.png]]&lt;/div&gt;</summary>
		<author><name>Nata</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1520</id>
		<title>Suchen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1520"/>
				<updated>2008-05-29T01:25:56Z</updated>
		
		<summary type="html">&lt;p&gt;Nata: /* Rotation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Es wäre super wenn jemand ganz kurz schreiben könnte, was am Donnerstag zu den Funktionen treeInsert/Remove/HasKey gemacht wurde, was man ja für den aktuellen Übungszettel braucht. Danke :-)&amp;lt;br /&amp;gt;&lt;br /&gt;
/edit: an eine Funktion &amp;quot;HasKey&amp;quot; kann ich mich aus der Vorlesung nicht erinnern. Wenn die jemand hat, bitte eintragen. - Protokollant&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--Hallo, ich kenne leider deinen Namen nicht, ich habe den Wiki-Eintrag von letztem Donnerstag gemacht.Da das Thema 'Dynamisches Array' noch Thema von letzter Woche war, hab ich die Wiederholung der amortisierten Kosten von diesem Mittwoch noch bei Effizienz eingetragen, du musst es also nicht noch mal in deinem Wiki eintragen. Du kannst dir aber gerne mal anschauen was ich geschrieben habe, vielleicht fallen dir noch ein paar Sachen ein, die man hätte besser machen können. lg, Franziska.--&amp;gt;&lt;br /&gt;
Das Suchen ist eine grundlegende Operation in der Informatik. Viele Probleme in der Informatik können auf Suchaufgaben zurückgeführt werden.&lt;br /&gt;
&lt;br /&gt;
Gemeint ist mit Suchen das Wiederauffinden einer oder mehrerer Datensätze aus einer Menge von früher gespeicherten Datensätzen. Ein paar einleitende Worte zum Suchproblem findet man [http://de.wikipedia.org/wiki/Suche hier].&lt;br /&gt;
&lt;br /&gt;
== Überblick verschiedener Suchmethoden ==&lt;br /&gt;
&lt;br /&gt;
Um sich der Vielseitigkeit von Suchproblemen bewusst zu werden, ist es sinnvoll, sich einen Überblick über verschiedene Suchmethoden zu verschaffen. &lt;br /&gt;
&lt;br /&gt;
Hier sei auch auf einen bereits existierenden Wikipedia-Artikel zu [http://de.wikipedia.org/wiki/Suchverfahren Suchverfahren] verwiesen.&lt;br /&gt;
&lt;br /&gt;
Allen gemeinsam ist die grundlegende Aufgabe, ein Datenelement mit bestimmten Eigenschaften aus einer großen Menge von Datenelementen zu selektieren.&lt;br /&gt;
Dies kann, natürlich ohne jeden Anspruch auf Vollständigkeit, nach einer der jetzt diskutierten Methoden geschehen:&lt;br /&gt;
&lt;br /&gt;
* '''Schlüsselsuche''': meint das Suchen von Elementen mit bestimmtem Schlüssel; ein klassisches Beispiel wäre das Suchen in einem Wörterbuch,  die Schlüssel entsprechen hier den Wörtern, die Datensätze wären die zu den Wörtern gehörigen Eintragungen.&lt;br /&gt;
&lt;br /&gt;
* '''Bereichssuche''': Im Allgemeinen meint die Bereichssuche in n-Dimensionen die Selektion von Elementen mit Eigenschaften aus einem bestimmten n-dimensionalen Volumen. Im eindimensionalen Fall will man alle Elemente finden, deren Eigenschaft(en) in einem bestimmten Intervall liegen. Die Verallgemeinerung auf n-Dimensionen ist offensichtlich. Ein Beispiel für die Bereichssuche in einer 3D-Kugel wäre ein Handy mit Geolokalisierung, welches alle Restaurants in einem Umkreis von 500m findet. Lineare Ungleichungen werden graphisch durch [http://de.wikipedia.org/wiki/Hyperebene Hyperebenen] repräsentiert. In 2D sind diese Hyperebenen Geraden. Die Ungleichungen können dann den Lösungsraum in irgendeiner Form begrenzen.&lt;br /&gt;
&lt;br /&gt;
* '''Ähnlichkeitssuche''': Finde Elemente, die gegebenen Eigenschaften möglichst ähnlich sind. Ein prominentes Beispiel ist Google (=Ähnlichkeit zwischen Suchbegriffen und Dokumenten) oder das Suchen des nächstengelegenen Restaurants (Ähnlichkeit zwischen eigener Position und Position des Restaurants). Ein wichtigster Spezialfall ist die ''nächste-nachbar Suche''.&lt;br /&gt;
&lt;br /&gt;
* '''Graphensuche''': Hier wäre beispielsweise das Problem optimaler Wege zu nennen (Navigationssuche). Dieser Punkt wird später im Verlauf der Vorlesung noch einmal aufgegriffen werden.&lt;br /&gt;
&lt;br /&gt;
Im jetzt Folgenden wird nur noch die ''Schlüsselsuche'' betrachtet werden.&lt;br /&gt;
&lt;br /&gt;
==Sequentielle Suche==&lt;br /&gt;
&lt;br /&gt;
Die ''sequentielle'' oder ''lineare'' Suche ist die 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 = sequentialSearch(a, key) &lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq &amp;lt;/math&amp;gt; foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def sequentialSearch(a, key):&lt;br /&gt;
    for i in range(len(a)):&lt;br /&gt;
        if a[i] == key:  # bzw. allgemeiner a[i].key == key &lt;br /&gt;
            return i&lt;br /&gt;
    return -1&lt;br /&gt;
&lt;br /&gt;
Wir wollen jetzt die Komplexität dieses Algorithmus bestimmen, wobei die Problemgröße durch &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt; gegeben ist. &lt;br /&gt;
&lt;br /&gt;
Dabei nimmt man an, dass der innerste Vergleich (a[i] == key) jeweils &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt; ist (diese Annahme könnte verletzt sein, wenn der Vergleichsoperator überladen ist und dadurch eine höhere Komplexität hat). Dieser Vergleich wird in der for-Schleife jeweils N-mal durchgeführt (&amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt;), so dass man nach der Verschachtelungsregel eine gesamte Komplexität von &amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt; erhält.&lt;br /&gt;
&lt;br /&gt;
Der Name ''lineare'' Suche rührt von diesem linearen Anwachsen der Komplexität mit der Arraygröße her.&lt;br /&gt;
&lt;br /&gt;
==Binäre Suche==&lt;br /&gt;
&lt;br /&gt;
Wie wir weiter unten zeigen werden, gestattet es diese Suchmethode, die Gesamtdauer der Suche in großen Datensätzen beträchtlich zu verringern. Die Methode beruht auf dem [http://de.wikipedia.org/wiki/Divide_and_Conquer Divide and Conquer-Prinzip], wobei die Suche in jedem Schritt rekursiv auf eine Hälfte des Datensatzes eingeschränkt wird. Weitere Details zur Methode sind [http://de.wikipedia.org/wiki/Bin%C3%A4re_Suche hier] zu finden. &lt;br /&gt;
&lt;br /&gt;
Die Methode ist nur dann anwendbar beziehungsweise effektiv, wenn folgendes gilt:&lt;br /&gt;
&lt;br /&gt;
# Auf der Eigenschaft der Daten, die zur Suche verwendet wird, ist eine Ordnung im Sinne von &amp;lt; oder &amp;gt; definiert.&lt;br /&gt;
# Wir wollen uns auf Datensätze beschränken, die schon fertig aufgebaut sind, in die also keine neuen Elemente mehr eingefügt werden, wenn man mit dem Suchen beginnt. Ist dies nicht der Fall, so ist unter Umständen die Implementierung über einen [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum Binärbaum] (siehe auch weiter unten) geschickter. &lt;br /&gt;
&lt;br /&gt;
Der folgende Algorithmus zeigt eine beispielhafte Implementierung der Methode (wir setzen wieder &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt;):&lt;br /&gt;
&lt;br /&gt;
 a = [...,...]     # array&lt;br /&gt;
 a.sort()   # sortiere über Ordnung des Schlüssels&lt;br /&gt;
 foundIndex = binSearch(a, key, 0, len(a))  # (Array, Schlüssel, von wo bis wo suchen im Array)&lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq&amp;lt;/math&amp;gt;  foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def binSearch(a, key, start, end):  # start ist 1. Index, end ist letzter Index + 1&lt;br /&gt;
    size = end - start   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if size &amp;lt;= 0:   # Bereich leer?  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return -1   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    center = (start + end)/2   # Integer Division, Ergebnis wird abgerundet, wichtig für ganzzahlige Indizes &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if a[center] == key:  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return center  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    elif a[center] &amp;lt; key:  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return binSearch(a, key, center + 1, end)  # Rekursion&lt;br /&gt;
    else:&lt;br /&gt;
        return binSearch(a, key, start + 1, center)  # Rekursion&lt;br /&gt;
&lt;br /&gt;
Zur Berechnung der Komplexität dieses Algorithmus vernachlässigen wir zunächst den Aufwand, den die Sortierung weiter oben verursacht. Dieser Schritt mag oder mag nicht zulässig sein.&lt;br /&gt;
&lt;br /&gt;
Nach der Sequenzregel haben auch alle &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;.  Es bleibt die Komplexität der Rekursion zu berechnen. Die gesamte Komplexität des Algorithmus (jetzt als Funktion f bezeichnet) setzt sich zusammen aus den oben erwähnten &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen sowie der Rekursion und ist &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f(N) = \mathcal{O}(1) + f(N/2) = \mathcal{O}(1) + \mathcal{O}(1) + f(N/4) = ... = \underbrace{\mathcal{O}(1) + ... + \mathcal{O}(1) + \underbrace{f(0)}_{\mathcal{O}(1)\, \rightarrow \,\mathrm{size-Abfrage}}}_{n+1 \,\mathrm{Terme}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Falls jetzt gilt &amp;lt;math&amp;gt; N = 2^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \rightarrow f(N) = \mathcal{O}(1) \cdot \mathcal{O}(n+1) = \mathcal{O}(n) = \mathcal{O}(\lg N) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Für große Datenmengen ist die ''binäre Suche'' also weit effizienter als die ''lineare Suche''. Verdoppelt sich beispielsweise die zu durchsuchende Datenmenge, so verdoppelt sich der Aufwand für die ''sequentielle Suche'' - bei der ''binären Suche'' hingegen benötigt man lediglich eine zusätzliche Vergleichsoperation. &lt;br /&gt;
&lt;br /&gt;
Für kleine Daten (&amp;lt;math&amp;gt; N = 4,\, 5 &amp;lt;/math&amp;gt;) ist die ''sequentielle Suche'' jedoch schneller als die ''binäre Suche'', da hier die rekursiven Funktionsaufrufe teurer als das Mehr an Vergleichen sind. Ein anderer ungünstiger Fall ist gegeben, wenn nur sehr wenige Suchanfragen erfolgen (weniger als &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; viele). Dann wird der Aufwand durch das Sortieren des Arrays dominiert, ist also &amp;lt;math&amp;gt;\mathcal{O}(N \lg N) &amp;lt;/math&amp;gt;. Auch dann ist sequentielle Suche vorzuziehen.&lt;br /&gt;
&lt;br /&gt;
Eine relativ einfache Möglichkeit, die ''binäre Suche'' zu verbessern, ist die sogenannte ''Interpolationssuche''. Hierbei wird die neue Position für die Suche, also die Mitte des Arrays, durch eine Schätzung ersetzt, die angibt, wo sich der Schlüssel innerhalb des Arrays befinden könnte. Bei der Suche in einem Telefonbuch nach dem Namen Zebra würde man ja auch nicht in der Mitte anfangen. Näheres hierzu im Buch von ''Sedgewick''.&lt;br /&gt;
&lt;br /&gt;
Um sich den Algorithmus der ''binären Suche'' klar zu machen, ist es instruktiv, sich die folgende Tabelle genauer anzusehen, die die sukzessive Belegung der Variablen bei verschiedenen Anfragen beschreibt. Die Testfälle wurden nach dem Prinzip des ''domain partitioning'' gewählt. Das zugehörige Array hat die Einträge&lt;br /&gt;
&lt;br /&gt;
 a = [2, 3, 4, 5, 6]&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align:center&amp;quot; border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;5&amp;quot; cellspacing=&amp;quot;0&amp;quot; &lt;br /&gt;
! gesuchter key   !!  start      !! end  !! size !! center !! return &amp;lt;br/&amp;gt; (-1 oder index)  !! Kommentare  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 4     ||0            || 5    ||  5   || 2   ||  2         || gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 2     || 0           || 5    ||  5   || 2   ||            || linker Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||           &lt;br /&gt;
|-&lt;br /&gt;
|       ||  0          || 1    ||  1   || 0   ||  0         || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 1     ||0            || 5    ||  5   || 2   ||            || links außerhalb&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 1    ||  1   || 0   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 0    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 6     ||0            || 5    ||  5   || 2   ||            || rechter Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    || 2    || 4   || 4          || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 5     ||0            || 5    ||  5   || 2   ||            || typischer Fall&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||3            || 5    ||  2   || 4   ||            ||  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       || 3           || 4    ||  1   || 3   || 3          || gefunden&lt;br /&gt;
|- &lt;br /&gt;
| 7     ||0            || 5    ||  5   || 2   ||            || rechts außerhalb        &lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    ||  2   || 4   ||            ||&lt;br /&gt;
|-&lt;br /&gt;
|       ||5            || 5    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Suche in einem Binärbaum ==&lt;br /&gt;
&lt;br /&gt;
Eine kurze Einführung in Binärbäume findet man [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum hier].&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum.png|text-top|300x300px|Zur Illustration von Bäumen]]&lt;br /&gt;
&lt;br /&gt;
Bäume sind zweidimensional verkettete Strukturen. Sie gehören zu den fundamentalen Datenstrukturen in der Informatik. Da man in Bäumen nicht nur Daten speichern kann, sondern auch relevante Beziehungen der Daten untereinander, festgelegt über eine Ordnung auf der vergleichenden Dateneigenschaft (''Schlüssel''), eignen sich Bäume also insbesondere, um gesuchte Daten schnell wieder auffinden zu können.&lt;br /&gt;
&lt;br /&gt;
Ein ''Binärbaum'' wie oben skizziert besteht aus einer Menge von ''Knoten'', die untereinander durch ''Kanten'' verbunden sind. Jeder Knoten hat einen linken und einen rechten Unterbaum, der auch leer sein kann (in Python ließe sich dies mit ''None'' implementieren). Führt eine Kante von Knoten A zu Knoten B, so heißt A Vater von B und B Kind von A. Es gibt genau einen Knoten ohne Vater, den man ''Wurzel'' nennt. Knoten ohne Kinder heißen ''Blätter''.&lt;br /&gt;
&lt;br /&gt;
Ein ''Suchbaum'' hat zusätzlich die Eigenschaft, dass die Schlüssel jedes Knotens sortiert sind. Alle Schlüssel im linken Unterbaum sind kleiner, alle Schlüssel im rechten Unterbaum sind größer als ihr Vater. Wir wollen hierbei annehmen, dass jeder Schlüssel pro Datensatz nur einmal vorkommt, da sich sonst die &amp;gt;- oder &amp;lt;-Relation nicht mehr strikt erfüllen ließe.&lt;br /&gt;
&lt;br /&gt;
Um in einem Baum suchen zu können, wollen wir von zwei Annahmen ausgehen:&lt;br /&gt;
# Einfügen und Suchen im Baum wechseln sich ab. (Wenn das Suchen erst beginnt, nachdem alle Einfügungen erfolgt sind, wäre ein dynamisches Array mit binärer Suche wie oben wesentlich einfacher.)&lt;br /&gt;
# Der Schlüssel, der die Anordnung bestimmt, kennt eine [http://de.wikipedia.org/wiki/Ordnungsrelation Ordnung] (&amp;lt;-Relation oder &amp;gt;-Relation).&lt;br /&gt;
&lt;br /&gt;
Der folgende Python-Code zeigt beispielhaft, wie man in einem Suchbaum suchen könnte. Der Konstruktor für einen Knoten des Suchbaums ließe sich zum Beispiel so implementieren:&lt;br /&gt;
 &lt;br /&gt;
 class Node:&lt;br /&gt;
     def __init__(self, key):&lt;br /&gt;
         self.key = key&lt;br /&gt;
         self.left = self.right = None&lt;br /&gt;
 &lt;br /&gt;
 root = ...    # Wurzel des Suchbaums&lt;br /&gt;
 nodeFound = treeSearch(root, key)   # None, falls nichts gefunden&lt;br /&gt;
 &lt;br /&gt;
 def treeSearch(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return None&lt;br /&gt;
     elif node.key == key:&lt;br /&gt;
         return node&lt;br /&gt;
     elif key &amp;lt; node.key:&lt;br /&gt;
         return treeSearch(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         return treeSearch(node.right, key)&lt;br /&gt;
&lt;br /&gt;
Daraus resultiert der folgende Suchalgorithmus:&lt;br /&gt;
&lt;br /&gt;
 def treeSort(node,array):     # dynamisches Array als 2. Argument&lt;br /&gt;
     if node is None:     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
         return None:&lt;br /&gt;
     treeSort(node.left, array)     # rekursiv&lt;br /&gt;
     array.append(node.key)     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
     treeSort(node.right, array)     # rekursiv&lt;br /&gt;
&lt;br /&gt;
Komplexität:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 f(N)=\mathcal{O}(1)+f(N_\mathrm{left})+f(N_\mathrm{right})=\mathcal{O}(1)+\mathcal{O}(1)+f(N_\mathrm{leftleft})+f(N_\mathrm{leftright})+\mathcal{O}(1)+f(N_\mathrm{rightleft})&lt;br /&gt;
 +f(N_\mathrm{leftright})=N\ast\mathcal{O}(1)=\mathcal{O}(N)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sortier-Pseudocode:&lt;br /&gt;
&lt;br /&gt;
 Sortieren:&lt;br /&gt;
     (Array) a     # unsortiert&lt;br /&gt;
     (tree) t     # zunächst leer&lt;br /&gt;
 (dynamisches Array) r     # später sortiert&lt;br /&gt;
 for e in a:&lt;br /&gt;
     t = treeInsert(t, e)&lt;br /&gt;
 treeSort(t, r)&lt;br /&gt;
&lt;br /&gt;
== Insert ==&lt;br /&gt;
&lt;br /&gt;
Was passiert wenn der key (Schlüssel) schon vorhanden ist?&lt;br /&gt;
* error (exception)&lt;br /&gt;
* nichts einfügen&lt;br /&gt;
* nichts einfügen aber einen boolean return zurückgeben (einfügen=true, false)&lt;br /&gt;
* nochmals eingefügt (z.B. in der Node Klasse)&lt;br /&gt;
&lt;br /&gt;
Wobei die ersten 3 Punkte zur Mengensemantik gehören und der letzte eine Multimenge ist.&lt;br /&gt;
&lt;br /&gt;
Algorithmus im zweiten Fall (nichts einfügen):&lt;br /&gt;
&lt;br /&gt;
 def treeInsert(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return Node(key)     # Alternative Schreibweise: node = Node(key)&lt;br /&gt;
     if node.key == key:      # und dann:&lt;br /&gt;
         return node          # pass&lt;br /&gt;
     elif key &amp;lt; node.key:     # links im Baum&lt;br /&gt;
         node.left = treeInsert(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         node.right = treeInsert(node.right, key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Remove ==&lt;br /&gt;
Fälle:&lt;br /&gt;
# key (bzw. Knoten der key enthält) ist ein Blatt =&amp;gt; einfach löschen&lt;br /&gt;
# node hat &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; linken Unterbaum oder &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; rechten Unterbaum =&amp;gt; durch Unterbaum ersetzen&lt;br /&gt;
# node hat beide Unterbäume:&lt;br /&gt;
#* Suche Vorgänger: max k &amp;lt; key (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; keys); Vorgänger ist immer ein Fall 1 oder Fall 2&lt;br /&gt;
#*:=&amp;gt; ersetze node durch Vorgänger und entferne Vorgänger&lt;br /&gt;
&lt;br /&gt;
 def treePredecessor(node):     # wird nur bei Fall 3 aufgerufen&lt;br /&gt;
     node = node.left&lt;br /&gt;
     while node.right is not None:&lt;br /&gt;
         node = node.right&lt;br /&gt;
     return node&lt;br /&gt;
 &lt;br /&gt;
 def treeRemove(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return node&lt;br /&gt;
     if key &amp;lt; node.key:&lt;br /&gt;
         node.left = treeRemove(node.left, key)&lt;br /&gt;
     elif key &amp;gt; node.key:&lt;br /&gt;
         node.right = treeRemove(node.right, key)&lt;br /&gt;
     else:&lt;br /&gt;
         if node.left is None and node.right is None:     # Fall 1&lt;br /&gt;
             node = None&lt;br /&gt;
         elif node.left is None:     # Fall 2&lt;br /&gt;
             node = node.right       # +&lt;br /&gt;
         elif node.right is None:    # Fall 2&lt;br /&gt;
             node = node.left&lt;br /&gt;
         else:&lt;br /&gt;
             pred = treePredecessor(node)&lt;br /&gt;
             node.key = pred.key&lt;br /&gt;
             node.left = treeRemove(node.left, pred.key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Komplexitätsanalyse ==&lt;br /&gt;
&lt;br /&gt;
* Pfad (Zwischen node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;): &lt;br /&gt;
**Folge von Knoten (node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt;,...,node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt;), sodass:&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;ki&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;ki+1&amp;lt;/sub&amp;gt; haben eine gemeinsame Kante.&lt;br /&gt;
[[Image:Baum_Pfad.png]]&lt;br /&gt;
(Ein Baum ist ein Graph, indem es zwischen beliebigen Knoten stets genau einen Pfad gibt.)&lt;br /&gt;
&lt;br /&gt;
* Länge eines Pfades: Anzahl der Kanten = Anzahl der Knoten - 1&lt;br /&gt;
* Tiefe eines Knotens: Pfadlänge vom Knoten zur Wurzel des Baumes.&lt;br /&gt;
* Tiefe des Baumes: maximale Tiefe eines Knotens&lt;br /&gt;
* Balance eines Baumes: maximale Tiefe(k) - minimale Tiefe(k) (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; Blätter)&lt;br /&gt;
&lt;br /&gt;
== Ungünstigster Fall von treeSearch ==&lt;br /&gt;
&lt;br /&gt;
Komplexität von treeSearch = Länge des Pfades zum Knoten wo &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; gefunden wird, oder erkannt wird, dass &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; nicht im Baum ist.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; wird nicht gefunden, aber für diese Entscheidung muss der Längste Pfad vollständig durchlaufen werden.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(T)&amp;lt;/math&amp;gt; wo T = Tiefe des Baumes&lt;br /&gt;
*: [1,2,3,4,5]:&amp;lt;br /&amp;gt; [[Image:Balance.png]]&lt;br /&gt;
*: Tiefe T = 4, Balance = 4&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; wo N = Anzahl der Elemente.&lt;br /&gt;
&lt;br /&gt;
== Aufgabe ==&lt;br /&gt;
&lt;br /&gt;
Minimiere Balance (erzeuge balancierten Baum):&lt;br /&gt;
# Einfügen in geschickter Reihenfolge (siehe Übungsaufgabe)&lt;br /&gt;
# Selbstbalancierter Baum:&lt;br /&gt;
#* Überprüfen der Balance nach jedem Einfügen&lt;br /&gt;
#* Umstrukturieren des Baumes, falls Balance &amp;gt; 1 (Suchbaum-Bedingung muss erhalten bleiben)&lt;br /&gt;
#* AVL-Bäume (älteste Variante)&lt;br /&gt;
#* Rot-Schwarz-Bäume (verbreiteste Variante)&lt;br /&gt;
#* Treaps (flexibelste Variante, siehe Übung)&lt;br /&gt;
#* Splay trees&lt;br /&gt;
#* Andersson Trees (einfachste Variante)&lt;br /&gt;
(#* Skip Lists (schnellste Variante, aber kein Binärbaum))&lt;br /&gt;
&lt;br /&gt;
== Umstrukturieren, so dass Suchbaumbedingung erhalten bleibt: ==&lt;br /&gt;
&lt;br /&gt;
Rotation: elementare Umstrukturierungen&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum_Rotation.png]]&lt;br /&gt;
&lt;br /&gt;
 def rotateRight(node):&lt;br /&gt;
     newRoot = node.left&lt;br /&gt;
     node.left = newRoot.right&lt;br /&gt;
     newRoot.right = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
 def rotateLeft(node):&lt;br /&gt;
     newRoot = node.right&lt;br /&gt;
     node.right = newRoot.left&lt;br /&gt;
     newRoot.left = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
== Balance eines Suchbaumes ==&lt;br /&gt;
&lt;br /&gt;
===Balance eines Baumes zu  definieren:===&lt;br /&gt;
[[Image:Abbildung1.jpg|250px|right]]&lt;br /&gt;
*betrachte &amp;lt;tt&amp;gt;None&amp;lt;/tt&amp;gt; als zusätzlichen Knoten : '''sentinel'''   ([[englische Sprache|engl.]] für ''Wächter'')&lt;br /&gt;
*oder defeniere speziellen sentinel-Knoten&lt;br /&gt;
*RS - Pfades : von &amp;lt;tt&amp;gt;root&amp;lt;/tt&amp;gt; &amp;amp;rarr; ''sentinel''&lt;br /&gt;
*Länge eines Pfades ''P''      '''''|P|'''''&lt;br /&gt;
* &amp;lt;u&amp;gt;die Balance eines Baumes zu defenieren:&amp;lt;/u&amp;gt;&lt;br /&gt;
: '''''B = max P '''''&amp;lt;math&amp;gt; \in &amp;lt;/math&amp;gt; '''''{RS}    |P| - min |P|'''''&lt;br /&gt;
:'''''{RS}'''''  Menge aller RS-Pfade&lt;br /&gt;
;vollständiger Baum:  Balance &amp;lt;math&amp;gt;~=0&amp;lt;/math&amp;gt;&lt;br /&gt;
:Alle Knoten(außer Blättern) haben 2 Kinder.&lt;br /&gt;
;perfect balancierter Baum:  Balance  &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 1&lt;br /&gt;
'''alternative Defenition für perfect balancierte Bäume'''&lt;br /&gt;
:Für jeden Teilbaum gilt es: rechtes und linkes Kind (für jeden Knoten) sind auch wieder perfect balancierte Bäume und ihre Höhe unterscheidet sich höchstens um '''1'''.&lt;br /&gt;
&lt;br /&gt;
===Größe===&lt;br /&gt;
[[Image:baum-v.png|700px|right]]&lt;br /&gt;
====Größe des vollständigen Baumes====&lt;br /&gt;
Ebene K hat 2 &amp;lt;sup&amp;gt;k&amp;lt;/sup&amp;gt; Knoten.&lt;br /&gt;
Falls Tiefe = d , dann N = 2&amp;lt;sup&amp;gt;0&amp;lt;/sup&amp;gt; + 2&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;.....+ 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; = 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1&lt;br /&gt;
&lt;br /&gt;
====Größe des perfect balancierten Baumes====&lt;br /&gt;
Die Tiefe d, kann nicht besser sein als vollständiger Baum&lt;br /&gt;
:'''=&amp;gt;'''N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2 &amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
;schlechteste perfect balancierter Baum : ist ein vollständiger der Tiefe  d - 1 + 1, N &amp;lt;math&amp;gt; \ge&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:'''=&amp;gt;'''  2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1)   &amp;lt;math&amp;gt;~&amp;lt;&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt;)&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
d &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt;  log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; (d + 1)&lt;br /&gt;
&lt;br /&gt;
*Ergibt die Komplexität der Suche im schlechtesten Fall: Anzahl der Vergleiche pro Knoten( = 2 bzw. = 3)&amp;lt;math&amp;gt;\ast&amp;lt;/math&amp;gt;Anzahl der Knoten&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow ~f(N)\le 2d \le 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
*'''perfekt balancierter Baum '''=&amp;gt; AVL-Baum &lt;br /&gt;
*'''balancierter Baum''':&lt;br /&gt;
::&amp;lt;math&amp;gt;\forall  N:d(N)\le c \ast d (N)&amp;lt;/math&amp;gt; und  &amp;lt;math&amp;gt;1 \le c &amp;lt; \mathcal {1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:: d ist die Tiefe von perfekt balancierten Baum&lt;br /&gt;
&lt;br /&gt;
:''Komplexität der Suche'':&amp;lt;math&amp;gt; ~f(N)\le  c\ast 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
:algorithmisch einfacher als perfekt balancierter Baum, aber fast genauso schnell&lt;br /&gt;
&lt;br /&gt;
====Selbst-balancierter Baum====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  def insertTree(node,key):&lt;br /&gt;
    if node is sentinel:       #(None = sentinel)&lt;br /&gt;
        return Node(key)       &lt;br /&gt;
    if node.key == key:&lt;br /&gt;
        return Node&lt;br /&gt;
    if key &amp;lt; node.key:&lt;br /&gt;
        Node = insertTree(node.left, key)&lt;br /&gt;
    else:&lt;br /&gt;
        node.right = insertTree(node.right, key)&lt;br /&gt;
   #optimiere Balance:&lt;br /&gt;
        return node&lt;br /&gt;
&lt;br /&gt;
===Anderson-Bäume===&lt;br /&gt;
&lt;br /&gt;
  class Node:&lt;br /&gt;
    def__init__(self, key):&lt;br /&gt;
        self.key = key&lt;br /&gt;
        self.left = selft.right = None&lt;br /&gt;
        #einfügen:&lt;br /&gt;
        self.level = 1&lt;br /&gt;
&lt;br /&gt;
:''level'' : kodiert Abstand von sentinel&lt;br /&gt;
====Regeln====&lt;br /&gt;
Es gibt vertikale Kanten(parent.level == child.level + 1 ) und horizontale Kanten(parent.level == child.level)&lt;br /&gt;
:*verfeinerter Regel :  ''level kodiert reduzierten Abstand von Setinel''  (d.b. horizontale Kanten werden nicht gezählt)&lt;br /&gt;
::(die nächste zwei Regeln sichern die Balance):&lt;br /&gt;
::*alle RS-Pfade haben die gleiche reduzierte Länge oder ''root'' hat bei allen Pfaden das gleiche Level&lt;br /&gt;
::*kein Pfad hat 2 aufeinanderer folgende horizontale Kanten&lt;br /&gt;
:*nur Kanten zum rechten Kind dürfen horizontal sein&lt;br /&gt;
:*die reduzierte Höhe jedes Blatts ist '''hr=1'''&lt;br /&gt;
&lt;br /&gt;
====Beweis====&lt;br /&gt;
Vereinfachung des Algorithmus&lt;br /&gt;
:'''Satz''':ein Anderson-Baum ist balanciert.&lt;br /&gt;
*1. Sei '''hr'''- die reduzierte Höhe&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; jeder Teilbaum enthält mindestens &amp;lt;math&amp;gt;N\ge 2^{hr} -1 &amp;lt;/math&amp;gt;  Knoten&lt;br /&gt;
&lt;br /&gt;
::a). Blätter : reduzierte Höhe 1 =&amp;gt; &amp;lt;math&amp;gt; N\ge 2^{1} - 1 = 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
::b). inneren Knoten: jeder Unterbaum hat mindestens reduzierte Höhe &amp;lt;math&amp;gt; ~hr - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt;  jeder Unterbaum hat mindestens &amp;lt;math&amp;gt; ~2^{hr} -1 &amp;lt;/math&amp;gt; Knoten&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt;N \ge 2 (2^{hr-1} -1)+1 = 2^w - 2 + 1 = 2^{hr} -1&amp;lt;/math&amp;gt;&lt;br /&gt;
::::alle RS-Pfade heben gleiche Länge&lt;br /&gt;
[[Image:Ander.jpg|450px|right]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
*2. Kein Pfad hat 2 aufeinanderfolgende horizontal Kanten&lt;br /&gt;
:::&amp;lt;math&amp;gt;\Rightarrow  d \le 2 hr + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
*3. zusammen:&lt;br /&gt;
:&amp;lt;math&amp;gt;N \ge 2^{\frac {d}{2}} - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;log_{2}{ N}+1\ge log 2^{\frac {d}{2}} - 1 \ge \frac {d}{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; ~d &amp;lt; 2 log_{2}{(N + 1)}&amp;lt;/math&amp;gt;     - balancierter Baum&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
:'''Suchzeit''' : &amp;lt;math&amp;gt; ~ f(N) = \mathcal{O}(log{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Wie erreicht man die Balance===&lt;br /&gt;
====Rotation====&lt;br /&gt;
[[Image:Abbildung4.jpg|text-top|500px|Illustration von Rotation]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
   def rotateRight (node):&lt;br /&gt;
      root = node.left&lt;br /&gt;
      node.left = node.right&lt;br /&gt;
      root.right = node&lt;br /&gt;
      return root&lt;br /&gt;
   def rotateLeft(node):&lt;br /&gt;
      root = node.right&lt;br /&gt;
      node.right = node.left&lt;br /&gt;
      root.left = node&lt;br /&gt;
      return root&lt;br /&gt;
&lt;br /&gt;
====Optimierung der Balance====&lt;br /&gt;
   &lt;br /&gt;
&lt;br /&gt;
       if node.left is not sentinel and node.level==node.left.level:&lt;br /&gt;
            node &amp;gt; rotateRight(node)&lt;br /&gt;
       if node.right is not sentinel and node.right.right is  not sentinel and node.level==rotate.right.right.level:&lt;br /&gt;
            node = rotateLeft(node)&lt;br /&gt;
            node.level += 1&lt;br /&gt;
[[Image:rotate.jpg|text-top]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
[[Image:liste.png]]&lt;/div&gt;</summary>
		<author><name>Nata</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1519</id>
		<title>Suchen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1519"/>
				<updated>2008-05-29T01:25:36Z</updated>
		
		<summary type="html">&lt;p&gt;Nata: /* Rotation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Es wäre super wenn jemand ganz kurz schreiben könnte, was am Donnerstag zu den Funktionen treeInsert/Remove/HasKey gemacht wurde, was man ja für den aktuellen Übungszettel braucht. Danke :-)&amp;lt;br /&amp;gt;&lt;br /&gt;
/edit: an eine Funktion &amp;quot;HasKey&amp;quot; kann ich mich aus der Vorlesung nicht erinnern. Wenn die jemand hat, bitte eintragen. - Protokollant&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--Hallo, ich kenne leider deinen Namen nicht, ich habe den Wiki-Eintrag von letztem Donnerstag gemacht.Da das Thema 'Dynamisches Array' noch Thema von letzter Woche war, hab ich die Wiederholung der amortisierten Kosten von diesem Mittwoch noch bei Effizienz eingetragen, du musst es also nicht noch mal in deinem Wiki eintragen. Du kannst dir aber gerne mal anschauen was ich geschrieben habe, vielleicht fallen dir noch ein paar Sachen ein, die man hätte besser machen können. lg, Franziska.--&amp;gt;&lt;br /&gt;
Das Suchen ist eine grundlegende Operation in der Informatik. Viele Probleme in der Informatik können auf Suchaufgaben zurückgeführt werden.&lt;br /&gt;
&lt;br /&gt;
Gemeint ist mit Suchen das Wiederauffinden einer oder mehrerer Datensätze aus einer Menge von früher gespeicherten Datensätzen. Ein paar einleitende Worte zum Suchproblem findet man [http://de.wikipedia.org/wiki/Suche hier].&lt;br /&gt;
&lt;br /&gt;
== Überblick verschiedener Suchmethoden ==&lt;br /&gt;
&lt;br /&gt;
Um sich der Vielseitigkeit von Suchproblemen bewusst zu werden, ist es sinnvoll, sich einen Überblick über verschiedene Suchmethoden zu verschaffen. &lt;br /&gt;
&lt;br /&gt;
Hier sei auch auf einen bereits existierenden Wikipedia-Artikel zu [http://de.wikipedia.org/wiki/Suchverfahren Suchverfahren] verwiesen.&lt;br /&gt;
&lt;br /&gt;
Allen gemeinsam ist die grundlegende Aufgabe, ein Datenelement mit bestimmten Eigenschaften aus einer großen Menge von Datenelementen zu selektieren.&lt;br /&gt;
Dies kann, natürlich ohne jeden Anspruch auf Vollständigkeit, nach einer der jetzt diskutierten Methoden geschehen:&lt;br /&gt;
&lt;br /&gt;
* '''Schlüsselsuche''': meint das Suchen von Elementen mit bestimmtem Schlüssel; ein klassisches Beispiel wäre das Suchen in einem Wörterbuch,  die Schlüssel entsprechen hier den Wörtern, die Datensätze wären die zu den Wörtern gehörigen Eintragungen.&lt;br /&gt;
&lt;br /&gt;
* '''Bereichssuche''': Im Allgemeinen meint die Bereichssuche in n-Dimensionen die Selektion von Elementen mit Eigenschaften aus einem bestimmten n-dimensionalen Volumen. Im eindimensionalen Fall will man alle Elemente finden, deren Eigenschaft(en) in einem bestimmten Intervall liegen. Die Verallgemeinerung auf n-Dimensionen ist offensichtlich. Ein Beispiel für die Bereichssuche in einer 3D-Kugel wäre ein Handy mit Geolokalisierung, welches alle Restaurants in einem Umkreis von 500m findet. Lineare Ungleichungen werden graphisch durch [http://de.wikipedia.org/wiki/Hyperebene Hyperebenen] repräsentiert. In 2D sind diese Hyperebenen Geraden. Die Ungleichungen können dann den Lösungsraum in irgendeiner Form begrenzen.&lt;br /&gt;
&lt;br /&gt;
* '''Ähnlichkeitssuche''': Finde Elemente, die gegebenen Eigenschaften möglichst ähnlich sind. Ein prominentes Beispiel ist Google (=Ähnlichkeit zwischen Suchbegriffen und Dokumenten) oder das Suchen des nächstengelegenen Restaurants (Ähnlichkeit zwischen eigener Position und Position des Restaurants). Ein wichtigster Spezialfall ist die ''nächste-nachbar Suche''.&lt;br /&gt;
&lt;br /&gt;
* '''Graphensuche''': Hier wäre beispielsweise das Problem optimaler Wege zu nennen (Navigationssuche). Dieser Punkt wird später im Verlauf der Vorlesung noch einmal aufgegriffen werden.&lt;br /&gt;
&lt;br /&gt;
Im jetzt Folgenden wird nur noch die ''Schlüsselsuche'' betrachtet werden.&lt;br /&gt;
&lt;br /&gt;
==Sequentielle Suche==&lt;br /&gt;
&lt;br /&gt;
Die ''sequentielle'' oder ''lineare'' Suche ist die 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 = sequentialSearch(a, key) &lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq &amp;lt;/math&amp;gt; foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def sequentialSearch(a, key):&lt;br /&gt;
    for i in range(len(a)):&lt;br /&gt;
        if a[i] == key:  # bzw. allgemeiner a[i].key == key &lt;br /&gt;
            return i&lt;br /&gt;
    return -1&lt;br /&gt;
&lt;br /&gt;
Wir wollen jetzt die Komplexität dieses Algorithmus bestimmen, wobei die Problemgröße durch &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt; gegeben ist. &lt;br /&gt;
&lt;br /&gt;
Dabei nimmt man an, dass der innerste Vergleich (a[i] == key) jeweils &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt; ist (diese Annahme könnte verletzt sein, wenn der Vergleichsoperator überladen ist und dadurch eine höhere Komplexität hat). Dieser Vergleich wird in der for-Schleife jeweils N-mal durchgeführt (&amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt;), so dass man nach der Verschachtelungsregel eine gesamte Komplexität von &amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt; erhält.&lt;br /&gt;
&lt;br /&gt;
Der Name ''lineare'' Suche rührt von diesem linearen Anwachsen der Komplexität mit der Arraygröße her.&lt;br /&gt;
&lt;br /&gt;
==Binäre Suche==&lt;br /&gt;
&lt;br /&gt;
Wie wir weiter unten zeigen werden, gestattet es diese Suchmethode, die Gesamtdauer der Suche in großen Datensätzen beträchtlich zu verringern. Die Methode beruht auf dem [http://de.wikipedia.org/wiki/Divide_and_Conquer Divide and Conquer-Prinzip], wobei die Suche in jedem Schritt rekursiv auf eine Hälfte des Datensatzes eingeschränkt wird. Weitere Details zur Methode sind [http://de.wikipedia.org/wiki/Bin%C3%A4re_Suche hier] zu finden. &lt;br /&gt;
&lt;br /&gt;
Die Methode ist nur dann anwendbar beziehungsweise effektiv, wenn folgendes gilt:&lt;br /&gt;
&lt;br /&gt;
# Auf der Eigenschaft der Daten, die zur Suche verwendet wird, ist eine Ordnung im Sinne von &amp;lt; oder &amp;gt; definiert.&lt;br /&gt;
# Wir wollen uns auf Datensätze beschränken, die schon fertig aufgebaut sind, in die also keine neuen Elemente mehr eingefügt werden, wenn man mit dem Suchen beginnt. Ist dies nicht der Fall, so ist unter Umständen die Implementierung über einen [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum Binärbaum] (siehe auch weiter unten) geschickter. &lt;br /&gt;
&lt;br /&gt;
Der folgende Algorithmus zeigt eine beispielhafte Implementierung der Methode (wir setzen wieder &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt;):&lt;br /&gt;
&lt;br /&gt;
 a = [...,...]     # array&lt;br /&gt;
 a.sort()   # sortiere über Ordnung des Schlüssels&lt;br /&gt;
 foundIndex = binSearch(a, key, 0, len(a))  # (Array, Schlüssel, von wo bis wo suchen im Array)&lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq&amp;lt;/math&amp;gt;  foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def binSearch(a, key, start, end):  # start ist 1. Index, end ist letzter Index + 1&lt;br /&gt;
    size = end - start   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if size &amp;lt;= 0:   # Bereich leer?  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return -1   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    center = (start + end)/2   # Integer Division, Ergebnis wird abgerundet, wichtig für ganzzahlige Indizes &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if a[center] == key:  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return center  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    elif a[center] &amp;lt; key:  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return binSearch(a, key, center + 1, end)  # Rekursion&lt;br /&gt;
    else:&lt;br /&gt;
        return binSearch(a, key, start + 1, center)  # Rekursion&lt;br /&gt;
&lt;br /&gt;
Zur Berechnung der Komplexität dieses Algorithmus vernachlässigen wir zunächst den Aufwand, den die Sortierung weiter oben verursacht. Dieser Schritt mag oder mag nicht zulässig sein.&lt;br /&gt;
&lt;br /&gt;
Nach der Sequenzregel haben auch alle &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;.  Es bleibt die Komplexität der Rekursion zu berechnen. Die gesamte Komplexität des Algorithmus (jetzt als Funktion f bezeichnet) setzt sich zusammen aus den oben erwähnten &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen sowie der Rekursion und ist &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f(N) = \mathcal{O}(1) + f(N/2) = \mathcal{O}(1) + \mathcal{O}(1) + f(N/4) = ... = \underbrace{\mathcal{O}(1) + ... + \mathcal{O}(1) + \underbrace{f(0)}_{\mathcal{O}(1)\, \rightarrow \,\mathrm{size-Abfrage}}}_{n+1 \,\mathrm{Terme}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Falls jetzt gilt &amp;lt;math&amp;gt; N = 2^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \rightarrow f(N) = \mathcal{O}(1) \cdot \mathcal{O}(n+1) = \mathcal{O}(n) = \mathcal{O}(\lg N) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Für große Datenmengen ist die ''binäre Suche'' also weit effizienter als die ''lineare Suche''. Verdoppelt sich beispielsweise die zu durchsuchende Datenmenge, so verdoppelt sich der Aufwand für die ''sequentielle Suche'' - bei der ''binären Suche'' hingegen benötigt man lediglich eine zusätzliche Vergleichsoperation. &lt;br /&gt;
&lt;br /&gt;
Für kleine Daten (&amp;lt;math&amp;gt; N = 4,\, 5 &amp;lt;/math&amp;gt;) ist die ''sequentielle Suche'' jedoch schneller als die ''binäre Suche'', da hier die rekursiven Funktionsaufrufe teurer als das Mehr an Vergleichen sind. Ein anderer ungünstiger Fall ist gegeben, wenn nur sehr wenige Suchanfragen erfolgen (weniger als &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; viele). Dann wird der Aufwand durch das Sortieren des Arrays dominiert, ist also &amp;lt;math&amp;gt;\mathcal{O}(N \lg N) &amp;lt;/math&amp;gt;. Auch dann ist sequentielle Suche vorzuziehen.&lt;br /&gt;
&lt;br /&gt;
Eine relativ einfache Möglichkeit, die ''binäre Suche'' zu verbessern, ist die sogenannte ''Interpolationssuche''. Hierbei wird die neue Position für die Suche, also die Mitte des Arrays, durch eine Schätzung ersetzt, die angibt, wo sich der Schlüssel innerhalb des Arrays befinden könnte. Bei der Suche in einem Telefonbuch nach dem Namen Zebra würde man ja auch nicht in der Mitte anfangen. Näheres hierzu im Buch von ''Sedgewick''.&lt;br /&gt;
&lt;br /&gt;
Um sich den Algorithmus der ''binären Suche'' klar zu machen, ist es instruktiv, sich die folgende Tabelle genauer anzusehen, die die sukzessive Belegung der Variablen bei verschiedenen Anfragen beschreibt. Die Testfälle wurden nach dem Prinzip des ''domain partitioning'' gewählt. Das zugehörige Array hat die Einträge&lt;br /&gt;
&lt;br /&gt;
 a = [2, 3, 4, 5, 6]&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align:center&amp;quot; border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;5&amp;quot; cellspacing=&amp;quot;0&amp;quot; &lt;br /&gt;
! gesuchter key   !!  start      !! end  !! size !! center !! return &amp;lt;br/&amp;gt; (-1 oder index)  !! Kommentare  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 4     ||0            || 5    ||  5   || 2   ||  2         || gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 2     || 0           || 5    ||  5   || 2   ||            || linker Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||           &lt;br /&gt;
|-&lt;br /&gt;
|       ||  0          || 1    ||  1   || 0   ||  0         || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 1     ||0            || 5    ||  5   || 2   ||            || links außerhalb&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 1    ||  1   || 0   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 0    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 6     ||0            || 5    ||  5   || 2   ||            || rechter Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    || 2    || 4   || 4          || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 5     ||0            || 5    ||  5   || 2   ||            || typischer Fall&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||3            || 5    ||  2   || 4   ||            ||  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       || 3           || 4    ||  1   || 3   || 3          || gefunden&lt;br /&gt;
|- &lt;br /&gt;
| 7     ||0            || 5    ||  5   || 2   ||            || rechts außerhalb        &lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    ||  2   || 4   ||            ||&lt;br /&gt;
|-&lt;br /&gt;
|       ||5            || 5    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Suche in einem Binärbaum ==&lt;br /&gt;
&lt;br /&gt;
Eine kurze Einführung in Binärbäume findet man [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum hier].&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum.png|text-top|300x300px|Zur Illustration von Bäumen]]&lt;br /&gt;
&lt;br /&gt;
Bäume sind zweidimensional verkettete Strukturen. Sie gehören zu den fundamentalen Datenstrukturen in der Informatik. Da man in Bäumen nicht nur Daten speichern kann, sondern auch relevante Beziehungen der Daten untereinander, festgelegt über eine Ordnung auf der vergleichenden Dateneigenschaft (''Schlüssel''), eignen sich Bäume also insbesondere, um gesuchte Daten schnell wieder auffinden zu können.&lt;br /&gt;
&lt;br /&gt;
Ein ''Binärbaum'' wie oben skizziert besteht aus einer Menge von ''Knoten'', die untereinander durch ''Kanten'' verbunden sind. Jeder Knoten hat einen linken und einen rechten Unterbaum, der auch leer sein kann (in Python ließe sich dies mit ''None'' implementieren). Führt eine Kante von Knoten A zu Knoten B, so heißt A Vater von B und B Kind von A. Es gibt genau einen Knoten ohne Vater, den man ''Wurzel'' nennt. Knoten ohne Kinder heißen ''Blätter''.&lt;br /&gt;
&lt;br /&gt;
Ein ''Suchbaum'' hat zusätzlich die Eigenschaft, dass die Schlüssel jedes Knotens sortiert sind. Alle Schlüssel im linken Unterbaum sind kleiner, alle Schlüssel im rechten Unterbaum sind größer als ihr Vater. Wir wollen hierbei annehmen, dass jeder Schlüssel pro Datensatz nur einmal vorkommt, da sich sonst die &amp;gt;- oder &amp;lt;-Relation nicht mehr strikt erfüllen ließe.&lt;br /&gt;
&lt;br /&gt;
Um in einem Baum suchen zu können, wollen wir von zwei Annahmen ausgehen:&lt;br /&gt;
# Einfügen und Suchen im Baum wechseln sich ab. (Wenn das Suchen erst beginnt, nachdem alle Einfügungen erfolgt sind, wäre ein dynamisches Array mit binärer Suche wie oben wesentlich einfacher.)&lt;br /&gt;
# Der Schlüssel, der die Anordnung bestimmt, kennt eine [http://de.wikipedia.org/wiki/Ordnungsrelation Ordnung] (&amp;lt;-Relation oder &amp;gt;-Relation).&lt;br /&gt;
&lt;br /&gt;
Der folgende Python-Code zeigt beispielhaft, wie man in einem Suchbaum suchen könnte. Der Konstruktor für einen Knoten des Suchbaums ließe sich zum Beispiel so implementieren:&lt;br /&gt;
 &lt;br /&gt;
 class Node:&lt;br /&gt;
     def __init__(self, key):&lt;br /&gt;
         self.key = key&lt;br /&gt;
         self.left = self.right = None&lt;br /&gt;
 &lt;br /&gt;
 root = ...    # Wurzel des Suchbaums&lt;br /&gt;
 nodeFound = treeSearch(root, key)   # None, falls nichts gefunden&lt;br /&gt;
 &lt;br /&gt;
 def treeSearch(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return None&lt;br /&gt;
     elif node.key == key:&lt;br /&gt;
         return node&lt;br /&gt;
     elif key &amp;lt; node.key:&lt;br /&gt;
         return treeSearch(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         return treeSearch(node.right, key)&lt;br /&gt;
&lt;br /&gt;
Daraus resultiert der folgende Suchalgorithmus:&lt;br /&gt;
&lt;br /&gt;
 def treeSort(node,array):     # dynamisches Array als 2. Argument&lt;br /&gt;
     if node is None:     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
         return None:&lt;br /&gt;
     treeSort(node.left, array)     # rekursiv&lt;br /&gt;
     array.append(node.key)     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
     treeSort(node.right, array)     # rekursiv&lt;br /&gt;
&lt;br /&gt;
Komplexität:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 f(N)=\mathcal{O}(1)+f(N_\mathrm{left})+f(N_\mathrm{right})=\mathcal{O}(1)+\mathcal{O}(1)+f(N_\mathrm{leftleft})+f(N_\mathrm{leftright})+\mathcal{O}(1)+f(N_\mathrm{rightleft})&lt;br /&gt;
 +f(N_\mathrm{leftright})=N\ast\mathcal{O}(1)=\mathcal{O}(N)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sortier-Pseudocode:&lt;br /&gt;
&lt;br /&gt;
 Sortieren:&lt;br /&gt;
     (Array) a     # unsortiert&lt;br /&gt;
     (tree) t     # zunächst leer&lt;br /&gt;
 (dynamisches Array) r     # später sortiert&lt;br /&gt;
 for e in a:&lt;br /&gt;
     t = treeInsert(t, e)&lt;br /&gt;
 treeSort(t, r)&lt;br /&gt;
&lt;br /&gt;
== Insert ==&lt;br /&gt;
&lt;br /&gt;
Was passiert wenn der key (Schlüssel) schon vorhanden ist?&lt;br /&gt;
* error (exception)&lt;br /&gt;
* nichts einfügen&lt;br /&gt;
* nichts einfügen aber einen boolean return zurückgeben (einfügen=true, false)&lt;br /&gt;
* nochmals eingefügt (z.B. in der Node Klasse)&lt;br /&gt;
&lt;br /&gt;
Wobei die ersten 3 Punkte zur Mengensemantik gehören und der letzte eine Multimenge ist.&lt;br /&gt;
&lt;br /&gt;
Algorithmus im zweiten Fall (nichts einfügen):&lt;br /&gt;
&lt;br /&gt;
 def treeInsert(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return Node(key)     # Alternative Schreibweise: node = Node(key)&lt;br /&gt;
     if node.key == key:      # und dann:&lt;br /&gt;
         return node          # pass&lt;br /&gt;
     elif key &amp;lt; node.key:     # links im Baum&lt;br /&gt;
         node.left = treeInsert(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         node.right = treeInsert(node.right, key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Remove ==&lt;br /&gt;
Fälle:&lt;br /&gt;
# key (bzw. Knoten der key enthält) ist ein Blatt =&amp;gt; einfach löschen&lt;br /&gt;
# node hat &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; linken Unterbaum oder &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; rechten Unterbaum =&amp;gt; durch Unterbaum ersetzen&lt;br /&gt;
# node hat beide Unterbäume:&lt;br /&gt;
#* Suche Vorgänger: max k &amp;lt; key (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; keys); Vorgänger ist immer ein Fall 1 oder Fall 2&lt;br /&gt;
#*:=&amp;gt; ersetze node durch Vorgänger und entferne Vorgänger&lt;br /&gt;
&lt;br /&gt;
 def treePredecessor(node):     # wird nur bei Fall 3 aufgerufen&lt;br /&gt;
     node = node.left&lt;br /&gt;
     while node.right is not None:&lt;br /&gt;
         node = node.right&lt;br /&gt;
     return node&lt;br /&gt;
 &lt;br /&gt;
 def treeRemove(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return node&lt;br /&gt;
     if key &amp;lt; node.key:&lt;br /&gt;
         node.left = treeRemove(node.left, key)&lt;br /&gt;
     elif key &amp;gt; node.key:&lt;br /&gt;
         node.right = treeRemove(node.right, key)&lt;br /&gt;
     else:&lt;br /&gt;
         if node.left is None and node.right is None:     # Fall 1&lt;br /&gt;
             node = None&lt;br /&gt;
         elif node.left is None:     # Fall 2&lt;br /&gt;
             node = node.right       # +&lt;br /&gt;
         elif node.right is None:    # Fall 2&lt;br /&gt;
             node = node.left&lt;br /&gt;
         else:&lt;br /&gt;
             pred = treePredecessor(node)&lt;br /&gt;
             node.key = pred.key&lt;br /&gt;
             node.left = treeRemove(node.left, pred.key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Komplexitätsanalyse ==&lt;br /&gt;
&lt;br /&gt;
* Pfad (Zwischen node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;): &lt;br /&gt;
**Folge von Knoten (node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt;,...,node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt;), sodass:&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;ki&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;ki+1&amp;lt;/sub&amp;gt; haben eine gemeinsame Kante.&lt;br /&gt;
[[Image:Baum_Pfad.png]]&lt;br /&gt;
(Ein Baum ist ein Graph, indem es zwischen beliebigen Knoten stets genau einen Pfad gibt.)&lt;br /&gt;
&lt;br /&gt;
* Länge eines Pfades: Anzahl der Kanten = Anzahl der Knoten - 1&lt;br /&gt;
* Tiefe eines Knotens: Pfadlänge vom Knoten zur Wurzel des Baumes.&lt;br /&gt;
* Tiefe des Baumes: maximale Tiefe eines Knotens&lt;br /&gt;
* Balance eines Baumes: maximale Tiefe(k) - minimale Tiefe(k) (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; Blätter)&lt;br /&gt;
&lt;br /&gt;
== Ungünstigster Fall von treeSearch ==&lt;br /&gt;
&lt;br /&gt;
Komplexität von treeSearch = Länge des Pfades zum Knoten wo &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; gefunden wird, oder erkannt wird, dass &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; nicht im Baum ist.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; wird nicht gefunden, aber für diese Entscheidung muss der Längste Pfad vollständig durchlaufen werden.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(T)&amp;lt;/math&amp;gt; wo T = Tiefe des Baumes&lt;br /&gt;
*: [1,2,3,4,5]:&amp;lt;br /&amp;gt; [[Image:Balance.png]]&lt;br /&gt;
*: Tiefe T = 4, Balance = 4&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; wo N = Anzahl der Elemente.&lt;br /&gt;
&lt;br /&gt;
== Aufgabe ==&lt;br /&gt;
&lt;br /&gt;
Minimiere Balance (erzeuge balancierten Baum):&lt;br /&gt;
# Einfügen in geschickter Reihenfolge (siehe Übungsaufgabe)&lt;br /&gt;
# Selbstbalancierter Baum:&lt;br /&gt;
#* Überprüfen der Balance nach jedem Einfügen&lt;br /&gt;
#* Umstrukturieren des Baumes, falls Balance &amp;gt; 1 (Suchbaum-Bedingung muss erhalten bleiben)&lt;br /&gt;
#* AVL-Bäume (älteste Variante)&lt;br /&gt;
#* Rot-Schwarz-Bäume (verbreiteste Variante)&lt;br /&gt;
#* Treaps (flexibelste Variante, siehe Übung)&lt;br /&gt;
#* Splay trees&lt;br /&gt;
#* Andersson Trees (einfachste Variante)&lt;br /&gt;
(#* Skip Lists (schnellste Variante, aber kein Binärbaum))&lt;br /&gt;
&lt;br /&gt;
== Umstrukturieren, so dass Suchbaumbedingung erhalten bleibt: ==&lt;br /&gt;
&lt;br /&gt;
Rotation: elementare Umstrukturierungen&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum_Rotation.png]]&lt;br /&gt;
&lt;br /&gt;
 def rotateRight(node):&lt;br /&gt;
     newRoot = node.left&lt;br /&gt;
     node.left = newRoot.right&lt;br /&gt;
     newRoot.right = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
 def rotateLeft(node):&lt;br /&gt;
     newRoot = node.right&lt;br /&gt;
     node.right = newRoot.left&lt;br /&gt;
     newRoot.left = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
== Balance eines Suchbaumes ==&lt;br /&gt;
&lt;br /&gt;
===Balance eines Baumes zu  definieren:===&lt;br /&gt;
[[Image:Abbildung1.jpg|250px|right]]&lt;br /&gt;
*betrachte &amp;lt;tt&amp;gt;None&amp;lt;/tt&amp;gt; als zusätzlichen Knoten : '''sentinel'''   ([[englische Sprache|engl.]] für ''Wächter'')&lt;br /&gt;
*oder defeniere speziellen sentinel-Knoten&lt;br /&gt;
*RS - Pfades : von &amp;lt;tt&amp;gt;root&amp;lt;/tt&amp;gt; &amp;amp;rarr; ''sentinel''&lt;br /&gt;
*Länge eines Pfades ''P''      '''''|P|'''''&lt;br /&gt;
* &amp;lt;u&amp;gt;die Balance eines Baumes zu defenieren:&amp;lt;/u&amp;gt;&lt;br /&gt;
: '''''B = max P '''''&amp;lt;math&amp;gt; \in &amp;lt;/math&amp;gt; '''''{RS}    |P| - min |P|'''''&lt;br /&gt;
:'''''{RS}'''''  Menge aller RS-Pfade&lt;br /&gt;
;vollständiger Baum:  Balance &amp;lt;math&amp;gt;~=0&amp;lt;/math&amp;gt;&lt;br /&gt;
:Alle Knoten(außer Blättern) haben 2 Kinder.&lt;br /&gt;
;perfect balancierter Baum:  Balance  &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 1&lt;br /&gt;
'''alternative Defenition für perfect balancierte Bäume'''&lt;br /&gt;
:Für jeden Teilbaum gilt es: rechtes und linkes Kind (für jeden Knoten) sind auch wieder perfect balancierte Bäume und ihre Höhe unterscheidet sich höchstens um '''1'''.&lt;br /&gt;
&lt;br /&gt;
===Größe===&lt;br /&gt;
[[Image:baum-v.png|700px|right]]&lt;br /&gt;
====Größe des vollständigen Baumes====&lt;br /&gt;
Ebene K hat 2 &amp;lt;sup&amp;gt;k&amp;lt;/sup&amp;gt; Knoten.&lt;br /&gt;
Falls Tiefe = d , dann N = 2&amp;lt;sup&amp;gt;0&amp;lt;/sup&amp;gt; + 2&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;.....+ 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; = 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1&lt;br /&gt;
&lt;br /&gt;
====Größe des perfect balancierten Baumes====&lt;br /&gt;
Die Tiefe d, kann nicht besser sein als vollständiger Baum&lt;br /&gt;
:'''=&amp;gt;'''N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2 &amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
;schlechteste perfect balancierter Baum : ist ein vollständiger der Tiefe  d - 1 + 1, N &amp;lt;math&amp;gt; \ge&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:'''=&amp;gt;'''  2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1)   &amp;lt;math&amp;gt;~&amp;lt;&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt;)&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
d &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt;  log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; (d + 1)&lt;br /&gt;
&lt;br /&gt;
*Ergibt die Komplexität der Suche im schlechtesten Fall: Anzahl der Vergleiche pro Knoten( = 2 bzw. = 3)&amp;lt;math&amp;gt;\ast&amp;lt;/math&amp;gt;Anzahl der Knoten&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow ~f(N)\le 2d \le 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
*'''perfekt balancierter Baum '''=&amp;gt; AVL-Baum &lt;br /&gt;
*'''balancierter Baum''':&lt;br /&gt;
::&amp;lt;math&amp;gt;\forall  N:d(N)\le c \ast d (N)&amp;lt;/math&amp;gt; und  &amp;lt;math&amp;gt;1 \le c &amp;lt; \mathcal {1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:: d ist die Tiefe von perfekt balancierten Baum&lt;br /&gt;
&lt;br /&gt;
:''Komplexität der Suche'':&amp;lt;math&amp;gt; ~f(N)\le  c\ast 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
:algorithmisch einfacher als perfekt balancierter Baum, aber fast genauso schnell&lt;br /&gt;
&lt;br /&gt;
====Selbst-balancierter Baum====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  def insertTree(node,key):&lt;br /&gt;
    if node is sentinel:       #(None = sentinel)&lt;br /&gt;
        return Node(key)       &lt;br /&gt;
    if node.key == key:&lt;br /&gt;
        return Node&lt;br /&gt;
    if key &amp;lt; node.key:&lt;br /&gt;
        Node = insertTree(node.left, key)&lt;br /&gt;
    else:&lt;br /&gt;
        node.right = insertTree(node.right, key)&lt;br /&gt;
   #optimiere Balance:&lt;br /&gt;
        return node&lt;br /&gt;
&lt;br /&gt;
===Anderson-Bäume===&lt;br /&gt;
&lt;br /&gt;
  class Node:&lt;br /&gt;
    def__init__(self, key):&lt;br /&gt;
        self.key = key&lt;br /&gt;
        self.left = selft.right = None&lt;br /&gt;
        #einfügen:&lt;br /&gt;
        self.level = 1&lt;br /&gt;
&lt;br /&gt;
:''level'' : kodiert Abstand von sentinel&lt;br /&gt;
====Regeln====&lt;br /&gt;
Es gibt vertikale Kanten(parent.level == child.level + 1 ) und horizontale Kanten(parent.level == child.level)&lt;br /&gt;
:*verfeinerter Regel :  ''level kodiert reduzierten Abstand von Setinel''  (d.b. horizontale Kanten werden nicht gezählt)&lt;br /&gt;
::(die nächste zwei Regeln sichern die Balance):&lt;br /&gt;
::*alle RS-Pfade haben die gleiche reduzierte Länge oder ''root'' hat bei allen Pfaden das gleiche Level&lt;br /&gt;
::*kein Pfad hat 2 aufeinanderer folgende horizontale Kanten&lt;br /&gt;
:*nur Kanten zum rechten Kind dürfen horizontal sein&lt;br /&gt;
:*die reduzierte Höhe jedes Blatts ist '''hr=1'''&lt;br /&gt;
&lt;br /&gt;
====Beweis====&lt;br /&gt;
Vereinfachung des Algorithmus&lt;br /&gt;
:'''Satz''':ein Anderson-Baum ist balanciert.&lt;br /&gt;
*1. Sei '''hr'''- die reduzierte Höhe&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; jeder Teilbaum enthält mindestens &amp;lt;math&amp;gt;N\ge 2^{hr} -1 &amp;lt;/math&amp;gt;  Knoten&lt;br /&gt;
&lt;br /&gt;
::a). Blätter : reduzierte Höhe 1 =&amp;gt; &amp;lt;math&amp;gt; N\ge 2^{1} - 1 = 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
::b). inneren Knoten: jeder Unterbaum hat mindestens reduzierte Höhe &amp;lt;math&amp;gt; ~hr - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt;  jeder Unterbaum hat mindestens &amp;lt;math&amp;gt; ~2^{hr} -1 &amp;lt;/math&amp;gt; Knoten&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt;N \ge 2 (2^{hr-1} -1)+1 = 2^w - 2 + 1 = 2^{hr} -1&amp;lt;/math&amp;gt;&lt;br /&gt;
::::alle RS-Pfade heben gleiche Länge&lt;br /&gt;
[[Image:Ander.jpg|450px|right]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
*2. Kein Pfad hat 2 aufeinanderfolgende horizontal Kanten&lt;br /&gt;
:::&amp;lt;math&amp;gt;\Rightarrow  d \le 2 hr + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
*3. zusammen:&lt;br /&gt;
:&amp;lt;math&amp;gt;N \ge 2^{\frac {d}{2}} - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;log_{2}{ N}+1\ge log 2^{\frac {d}{2}} - 1 \ge \frac {d}{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; ~d &amp;lt; 2 log_{2}{(N + 1)}&amp;lt;/math&amp;gt;     - balancierter Baum&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
:'''Suchzeit''' : &amp;lt;math&amp;gt; ~ f(N) = \mathcal{O}(log{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Wie erreicht man die Balance===&lt;br /&gt;
====Rotation====&lt;br /&gt;
[[Image:Abbildung4.jpg|text-top|400px|Illustration von Rotation]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
   def rotateRight (node):&lt;br /&gt;
      root = node.left&lt;br /&gt;
      node.left = node.right&lt;br /&gt;
      root.right = node&lt;br /&gt;
      return root&lt;br /&gt;
   def rotateLeft(node):&lt;br /&gt;
      root = node.right&lt;br /&gt;
      node.right = node.left&lt;br /&gt;
      root.left = node&lt;br /&gt;
      return root&lt;br /&gt;
&lt;br /&gt;
====Optimierung der Balance====&lt;br /&gt;
   &lt;br /&gt;
&lt;br /&gt;
       if node.left is not sentinel and node.level==node.left.level:&lt;br /&gt;
            node &amp;gt; rotateRight(node)&lt;br /&gt;
       if node.right is not sentinel and node.right.right is  not sentinel and node.level==rotate.right.right.level:&lt;br /&gt;
            node = rotateLeft(node)&lt;br /&gt;
            node.level += 1&lt;br /&gt;
[[Image:rotate.jpg|text-top]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
[[Image:liste.png]]&lt;/div&gt;</summary>
		<author><name>Nata</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1518</id>
		<title>Suchen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1518"/>
				<updated>2008-05-29T01:24:55Z</updated>
		
		<summary type="html">&lt;p&gt;Nata: /* Beweis */&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 = sequentialSearch(a, key) &lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq &amp;lt;/math&amp;gt; foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def sequentialSearch(a, key):&lt;br /&gt;
    for i in range(len(a)):&lt;br /&gt;
        if a[i] == key:  # bzw. allgemeiner a[i].key == key &lt;br /&gt;
            return i&lt;br /&gt;
    return -1&lt;br /&gt;
&lt;br /&gt;
Wir wollen jetzt die Komplexität dieses Algorithmus bestimmen, wobei die Problemgröße durch &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt; gegeben ist. &lt;br /&gt;
&lt;br /&gt;
Dabei nimmt man an, dass der innerste Vergleich (a[i] == key) jeweils &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt; ist (diese Annahme könnte verletzt sein, wenn der Vergleichsoperator überladen ist und dadurch eine höhere Komplexität hat). Dieser Vergleich wird in der for-Schleife jeweils N-mal durchgeführt (&amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt;), so dass man nach der Verschachtelungsregel eine gesamte Komplexität von &amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt; erhält.&lt;br /&gt;
&lt;br /&gt;
Der Name ''lineare'' Suche rührt von diesem linearen Anwachsen der Komplexität mit der Arraygröße her.&lt;br /&gt;
&lt;br /&gt;
==Binäre Suche==&lt;br /&gt;
&lt;br /&gt;
Wie wir weiter unten zeigen werden, gestattet es diese Suchmethode, die Gesamtdauer der Suche in großen Datensätzen beträchtlich zu verringern. Die Methode beruht auf dem [http://de.wikipedia.org/wiki/Divide_and_Conquer Divide and Conquer-Prinzip], wobei die Suche in jedem Schritt rekursiv auf eine Hälfte des Datensatzes eingeschränkt wird. Weitere Details zur Methode sind [http://de.wikipedia.org/wiki/Bin%C3%A4re_Suche hier] zu finden. &lt;br /&gt;
&lt;br /&gt;
Die Methode ist nur dann anwendbar beziehungsweise effektiv, wenn folgendes gilt:&lt;br /&gt;
&lt;br /&gt;
# Auf der Eigenschaft der Daten, die zur Suche verwendet wird, ist eine Ordnung im Sinne von &amp;lt; oder &amp;gt; definiert.&lt;br /&gt;
# Wir wollen uns auf Datensätze beschränken, die schon fertig aufgebaut sind, in die also keine neuen Elemente mehr eingefügt werden, wenn man mit dem Suchen beginnt. Ist dies nicht der Fall, so ist unter Umständen die Implementierung über einen [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum Binärbaum] (siehe auch weiter unten) geschickter. &lt;br /&gt;
&lt;br /&gt;
Der folgende Algorithmus zeigt eine beispielhafte Implementierung der Methode (wir setzen wieder &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt;):&lt;br /&gt;
&lt;br /&gt;
 a = [...,...]     # array&lt;br /&gt;
 a.sort()   # sortiere über Ordnung des Schlüssels&lt;br /&gt;
 foundIndex = binSearch(a, key, 0, len(a))  # (Array, Schlüssel, von wo bis wo suchen im Array)&lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq&amp;lt;/math&amp;gt;  foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def binSearch(a, key, start, end):  # start ist 1. Index, end ist letzter Index + 1&lt;br /&gt;
    size = end - start   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if size &amp;lt;= 0:   # Bereich leer?  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return -1   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    center = (start + end)/2   # Integer Division, Ergebnis wird abgerundet, wichtig für ganzzahlige Indizes &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if a[center] == key:  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return center  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    elif a[center] &amp;lt; key:  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return binSearch(a, key, center + 1, end)  # Rekursion&lt;br /&gt;
    else:&lt;br /&gt;
        return binSearch(a, key, start + 1, center)  # Rekursion&lt;br /&gt;
&lt;br /&gt;
Zur Berechnung der Komplexität dieses Algorithmus vernachlässigen wir zunächst den Aufwand, den die Sortierung weiter oben verursacht. Dieser Schritt mag oder mag nicht zulässig sein.&lt;br /&gt;
&lt;br /&gt;
Nach der Sequenzregel haben auch alle &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;.  Es bleibt die Komplexität der Rekursion zu berechnen. Die gesamte Komplexität des Algorithmus (jetzt als Funktion f bezeichnet) setzt sich zusammen aus den oben erwähnten &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen sowie der Rekursion und ist &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f(N) = \mathcal{O}(1) + f(N/2) = \mathcal{O}(1) + \mathcal{O}(1) + f(N/4) = ... = \underbrace{\mathcal{O}(1) + ... + \mathcal{O}(1) + \underbrace{f(0)}_{\mathcal{O}(1)\, \rightarrow \,\mathrm{size-Abfrage}}}_{n+1 \,\mathrm{Terme}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Falls jetzt gilt &amp;lt;math&amp;gt; N = 2^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \rightarrow f(N) = \mathcal{O}(1) \cdot \mathcal{O}(n+1) = \mathcal{O}(n) = \mathcal{O}(\lg N) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Für große Datenmengen ist die ''binäre Suche'' also weit effizienter als die ''lineare Suche''. Verdoppelt sich beispielsweise die zu durchsuchende Datenmenge, so verdoppelt sich der Aufwand für die ''sequentielle Suche'' - bei der ''binären Suche'' hingegen benötigt man lediglich eine zusätzliche Vergleichsoperation. &lt;br /&gt;
&lt;br /&gt;
Für kleine Daten (&amp;lt;math&amp;gt; N = 4,\, 5 &amp;lt;/math&amp;gt;) ist die ''sequentielle Suche'' jedoch schneller als die ''binäre Suche'', da hier die rekursiven Funktionsaufrufe teurer als das Mehr an Vergleichen sind. Ein anderer ungünstiger Fall ist gegeben, wenn nur sehr wenige Suchanfragen erfolgen (weniger als &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; viele). Dann wird der Aufwand durch das Sortieren des Arrays dominiert, ist also &amp;lt;math&amp;gt;\mathcal{O}(N \lg N) &amp;lt;/math&amp;gt;. Auch dann ist sequentielle Suche vorzuziehen.&lt;br /&gt;
&lt;br /&gt;
Eine relativ einfache Möglichkeit, die ''binäre Suche'' zu verbessern, ist die sogenannte ''Interpolationssuche''. Hierbei wird die neue Position für die Suche, also die Mitte des Arrays, durch eine Schätzung ersetzt, die angibt, wo sich der Schlüssel innerhalb des Arrays befinden könnte. Bei der Suche in einem Telefonbuch nach dem Namen Zebra würde man ja auch nicht in der Mitte anfangen. Näheres hierzu im Buch von ''Sedgewick''.&lt;br /&gt;
&lt;br /&gt;
Um sich den Algorithmus der ''binären Suche'' klar zu machen, ist es instruktiv, sich die folgende Tabelle genauer anzusehen, die die sukzessive Belegung der Variablen bei verschiedenen Anfragen beschreibt. Die Testfälle wurden nach dem Prinzip des ''domain partitioning'' gewählt. Das zugehörige Array hat die Einträge&lt;br /&gt;
&lt;br /&gt;
 a = [2, 3, 4, 5, 6]&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align:center&amp;quot; border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;5&amp;quot; cellspacing=&amp;quot;0&amp;quot; &lt;br /&gt;
! gesuchter key   !!  start      !! end  !! size !! center !! return &amp;lt;br/&amp;gt; (-1 oder index)  !! Kommentare  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 4     ||0            || 5    ||  5   || 2   ||  2         || gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 2     || 0           || 5    ||  5   || 2   ||            || linker Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||           &lt;br /&gt;
|-&lt;br /&gt;
|       ||  0          || 1    ||  1   || 0   ||  0         || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 1     ||0            || 5    ||  5   || 2   ||            || links außerhalb&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 1    ||  1   || 0   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 0    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 6     ||0            || 5    ||  5   || 2   ||            || rechter Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    || 2    || 4   || 4          || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 5     ||0            || 5    ||  5   || 2   ||            || typischer Fall&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||3            || 5    ||  2   || 4   ||            ||  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       || 3           || 4    ||  1   || 3   || 3          || gefunden&lt;br /&gt;
|- &lt;br /&gt;
| 7     ||0            || 5    ||  5   || 2   ||            || rechts außerhalb        &lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    ||  2   || 4   ||            ||&lt;br /&gt;
|-&lt;br /&gt;
|       ||5            || 5    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Suche in einem Binärbaum ==&lt;br /&gt;
&lt;br /&gt;
Eine kurze Einführung in Binärbäume findet man [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum hier].&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum.png|text-top|300x300px|Zur Illustration von Bäumen]]&lt;br /&gt;
&lt;br /&gt;
Bäume sind zweidimensional verkettete Strukturen. Sie gehören zu den fundamentalen Datenstrukturen in der Informatik. Da man in Bäumen nicht nur Daten speichern kann, sondern auch relevante Beziehungen der Daten untereinander, festgelegt über eine Ordnung auf der vergleichenden Dateneigenschaft (''Schlüssel''), eignen sich Bäume also insbesondere, um gesuchte Daten schnell wieder auffinden zu können.&lt;br /&gt;
&lt;br /&gt;
Ein ''Binärbaum'' wie oben skizziert besteht aus einer Menge von ''Knoten'', die untereinander durch ''Kanten'' verbunden sind. Jeder Knoten hat einen linken und einen rechten Unterbaum, der auch leer sein kann (in Python ließe sich dies mit ''None'' implementieren). Führt eine Kante von Knoten A zu Knoten B, so heißt A Vater von B und B Kind von A. Es gibt genau einen Knoten ohne Vater, den man ''Wurzel'' nennt. Knoten ohne Kinder heißen ''Blätter''.&lt;br /&gt;
&lt;br /&gt;
Ein ''Suchbaum'' hat zusätzlich die Eigenschaft, dass die Schlüssel jedes Knotens sortiert sind. Alle Schlüssel im linken Unterbaum sind kleiner, alle Schlüssel im rechten Unterbaum sind größer als ihr Vater. Wir wollen hierbei annehmen, dass jeder Schlüssel pro Datensatz nur einmal vorkommt, da sich sonst die &amp;gt;- oder &amp;lt;-Relation nicht mehr strikt erfüllen ließe.&lt;br /&gt;
&lt;br /&gt;
Um in einem Baum suchen zu können, wollen wir von zwei Annahmen ausgehen:&lt;br /&gt;
# Einfügen und Suchen im Baum wechseln sich ab. (Wenn das Suchen erst beginnt, nachdem alle Einfügungen erfolgt sind, wäre ein dynamisches Array mit binärer Suche wie oben wesentlich einfacher.)&lt;br /&gt;
# Der Schlüssel, der die Anordnung bestimmt, kennt eine [http://de.wikipedia.org/wiki/Ordnungsrelation Ordnung] (&amp;lt;-Relation oder &amp;gt;-Relation).&lt;br /&gt;
&lt;br /&gt;
Der folgende Python-Code zeigt beispielhaft, wie man in einem Suchbaum suchen könnte. Der Konstruktor für einen Knoten des Suchbaums ließe sich zum Beispiel so implementieren:&lt;br /&gt;
 &lt;br /&gt;
 class Node:&lt;br /&gt;
     def __init__(self, key):&lt;br /&gt;
         self.key = key&lt;br /&gt;
         self.left = self.right = None&lt;br /&gt;
 &lt;br /&gt;
 root = ...    # Wurzel des Suchbaums&lt;br /&gt;
 nodeFound = treeSearch(root, key)   # None, falls nichts gefunden&lt;br /&gt;
 &lt;br /&gt;
 def treeSearch(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return None&lt;br /&gt;
     elif node.key == key:&lt;br /&gt;
         return node&lt;br /&gt;
     elif key &amp;lt; node.key:&lt;br /&gt;
         return treeSearch(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         return treeSearch(node.right, key)&lt;br /&gt;
&lt;br /&gt;
Daraus resultiert der folgende Suchalgorithmus:&lt;br /&gt;
&lt;br /&gt;
 def treeSort(node,array):     # dynamisches Array als 2. Argument&lt;br /&gt;
     if node is None:     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
         return None:&lt;br /&gt;
     treeSort(node.left, array)     # rekursiv&lt;br /&gt;
     array.append(node.key)     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
     treeSort(node.right, array)     # rekursiv&lt;br /&gt;
&lt;br /&gt;
Komplexität:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 f(N)=\mathcal{O}(1)+f(N_\mathrm{left})+f(N_\mathrm{right})=\mathcal{O}(1)+\mathcal{O}(1)+f(N_\mathrm{leftleft})+f(N_\mathrm{leftright})+\mathcal{O}(1)+f(N_\mathrm{rightleft})&lt;br /&gt;
 +f(N_\mathrm{leftright})=N\ast\mathcal{O}(1)=\mathcal{O}(N)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sortier-Pseudocode:&lt;br /&gt;
&lt;br /&gt;
 Sortieren:&lt;br /&gt;
     (Array) a     # unsortiert&lt;br /&gt;
     (tree) t     # zunächst leer&lt;br /&gt;
 (dynamisches Array) r     # später sortiert&lt;br /&gt;
 for e in a:&lt;br /&gt;
     t = treeInsert(t, e)&lt;br /&gt;
 treeSort(t, r)&lt;br /&gt;
&lt;br /&gt;
== Insert ==&lt;br /&gt;
&lt;br /&gt;
Was passiert wenn der key (Schlüssel) schon vorhanden ist?&lt;br /&gt;
* error (exception)&lt;br /&gt;
* nichts einfügen&lt;br /&gt;
* nichts einfügen aber einen boolean return zurückgeben (einfügen=true, false)&lt;br /&gt;
* nochmals eingefügt (z.B. in der Node Klasse)&lt;br /&gt;
&lt;br /&gt;
Wobei die ersten 3 Punkte zur Mengensemantik gehören und der letzte eine Multimenge ist.&lt;br /&gt;
&lt;br /&gt;
Algorithmus im zweiten Fall (nichts einfügen):&lt;br /&gt;
&lt;br /&gt;
 def treeInsert(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return Node(key)     # Alternative Schreibweise: node = Node(key)&lt;br /&gt;
     if node.key == key:      # und dann:&lt;br /&gt;
         return node          # pass&lt;br /&gt;
     elif key &amp;lt; node.key:     # links im Baum&lt;br /&gt;
         node.left = treeInsert(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         node.right = treeInsert(node.right, key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Remove ==&lt;br /&gt;
Fälle:&lt;br /&gt;
# key (bzw. Knoten der key enthält) ist ein Blatt =&amp;gt; einfach löschen&lt;br /&gt;
# node hat &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; linken Unterbaum oder &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; rechten Unterbaum =&amp;gt; durch Unterbaum ersetzen&lt;br /&gt;
# node hat beide Unterbäume:&lt;br /&gt;
#* Suche Vorgänger: max k &amp;lt; key (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; keys); Vorgänger ist immer ein Fall 1 oder Fall 2&lt;br /&gt;
#*:=&amp;gt; ersetze node durch Vorgänger und entferne Vorgänger&lt;br /&gt;
&lt;br /&gt;
 def treePredecessor(node):     # wird nur bei Fall 3 aufgerufen&lt;br /&gt;
     node = node.left&lt;br /&gt;
     while node.right is not None:&lt;br /&gt;
         node = node.right&lt;br /&gt;
     return node&lt;br /&gt;
 &lt;br /&gt;
 def treeRemove(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return node&lt;br /&gt;
     if key &amp;lt; node.key:&lt;br /&gt;
         node.left = treeRemove(node.left, key)&lt;br /&gt;
     elif key &amp;gt; node.key:&lt;br /&gt;
         node.right = treeRemove(node.right, key)&lt;br /&gt;
     else:&lt;br /&gt;
         if node.left is None and node.right is None:     # Fall 1&lt;br /&gt;
             node = None&lt;br /&gt;
         elif node.left is None:     # Fall 2&lt;br /&gt;
             node = node.right       # +&lt;br /&gt;
         elif node.right is None:    # Fall 2&lt;br /&gt;
             node = node.left&lt;br /&gt;
         else:&lt;br /&gt;
             pred = treePredecessor(node)&lt;br /&gt;
             node.key = pred.key&lt;br /&gt;
             node.left = treeRemove(node.left, pred.key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Komplexitätsanalyse ==&lt;br /&gt;
&lt;br /&gt;
* Pfad (Zwischen node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;): &lt;br /&gt;
**Folge von Knoten (node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt;,...,node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt;), sodass:&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;ki&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;ki+1&amp;lt;/sub&amp;gt; haben eine gemeinsame Kante.&lt;br /&gt;
[[Image:Baum_Pfad.png]]&lt;br /&gt;
(Ein Baum ist ein Graph, indem es zwischen beliebigen Knoten stets genau einen Pfad gibt.)&lt;br /&gt;
&lt;br /&gt;
* Länge eines Pfades: Anzahl der Kanten = Anzahl der Knoten - 1&lt;br /&gt;
* Tiefe eines Knotens: Pfadlänge vom Knoten zur Wurzel des Baumes.&lt;br /&gt;
* Tiefe des Baumes: maximale Tiefe eines Knotens&lt;br /&gt;
* Balance eines Baumes: maximale Tiefe(k) - minimale Tiefe(k) (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; Blätter)&lt;br /&gt;
&lt;br /&gt;
== Ungünstigster Fall von treeSearch ==&lt;br /&gt;
&lt;br /&gt;
Komplexität von treeSearch = Länge des Pfades zum Knoten wo &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; gefunden wird, oder erkannt wird, dass &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; nicht im Baum ist.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; wird nicht gefunden, aber für diese Entscheidung muss der Längste Pfad vollständig durchlaufen werden.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(T)&amp;lt;/math&amp;gt; wo T = Tiefe des Baumes&lt;br /&gt;
*: [1,2,3,4,5]:&amp;lt;br /&amp;gt; [[Image:Balance.png]]&lt;br /&gt;
*: Tiefe T = 4, Balance = 4&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; wo N = Anzahl der Elemente.&lt;br /&gt;
&lt;br /&gt;
== Aufgabe ==&lt;br /&gt;
&lt;br /&gt;
Minimiere Balance (erzeuge balancierten Baum):&lt;br /&gt;
# Einfügen in geschickter Reihenfolge (siehe Übungsaufgabe)&lt;br /&gt;
# Selbstbalancierter Baum:&lt;br /&gt;
#* Überprüfen der Balance nach jedem Einfügen&lt;br /&gt;
#* Umstrukturieren des Baumes, falls Balance &amp;gt; 1 (Suchbaum-Bedingung muss erhalten bleiben)&lt;br /&gt;
#* AVL-Bäume (älteste Variante)&lt;br /&gt;
#* Rot-Schwarz-Bäume (verbreiteste Variante)&lt;br /&gt;
#* Treaps (flexibelste Variante, siehe Übung)&lt;br /&gt;
#* Splay trees&lt;br /&gt;
#* Andersson Trees (einfachste Variante)&lt;br /&gt;
(#* Skip Lists (schnellste Variante, aber kein Binärbaum))&lt;br /&gt;
&lt;br /&gt;
== Umstrukturieren, so dass Suchbaumbedingung erhalten bleibt: ==&lt;br /&gt;
&lt;br /&gt;
Rotation: elementare Umstrukturierungen&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum_Rotation.png]]&lt;br /&gt;
&lt;br /&gt;
 def rotateRight(node):&lt;br /&gt;
     newRoot = node.left&lt;br /&gt;
     node.left = newRoot.right&lt;br /&gt;
     newRoot.right = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
 def rotateLeft(node):&lt;br /&gt;
     newRoot = node.right&lt;br /&gt;
     node.right = newRoot.left&lt;br /&gt;
     newRoot.left = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
== Balance eines Suchbaumes ==&lt;br /&gt;
&lt;br /&gt;
===Balance eines Baumes zu  definieren:===&lt;br /&gt;
[[Image:Abbildung1.jpg|250px|right]]&lt;br /&gt;
*betrachte &amp;lt;tt&amp;gt;None&amp;lt;/tt&amp;gt; als zusätzlichen Knoten : '''sentinel'''   ([[englische Sprache|engl.]] für ''Wächter'')&lt;br /&gt;
*oder defeniere speziellen sentinel-Knoten&lt;br /&gt;
*RS - Pfades : von &amp;lt;tt&amp;gt;root&amp;lt;/tt&amp;gt; &amp;amp;rarr; ''sentinel''&lt;br /&gt;
*Länge eines Pfades ''P''      '''''|P|'''''&lt;br /&gt;
* &amp;lt;u&amp;gt;die Balance eines Baumes zu defenieren:&amp;lt;/u&amp;gt;&lt;br /&gt;
: '''''B = max P '''''&amp;lt;math&amp;gt; \in &amp;lt;/math&amp;gt; '''''{RS}    |P| - min |P|'''''&lt;br /&gt;
:'''''{RS}'''''  Menge aller RS-Pfade&lt;br /&gt;
;vollständiger Baum:  Balance &amp;lt;math&amp;gt;~=0&amp;lt;/math&amp;gt;&lt;br /&gt;
:Alle Knoten(außer Blättern) haben 2 Kinder.&lt;br /&gt;
;perfect balancierter Baum:  Balance  &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 1&lt;br /&gt;
'''alternative Defenition für perfect balancierte Bäume'''&lt;br /&gt;
:Für jeden Teilbaum gilt es: rechtes und linkes Kind (für jeden Knoten) sind auch wieder perfect balancierte Bäume und ihre Höhe unterscheidet sich höchstens um '''1'''.&lt;br /&gt;
&lt;br /&gt;
===Größe===&lt;br /&gt;
[[Image:baum-v.png|700px|right]]&lt;br /&gt;
====Größe des vollständigen Baumes====&lt;br /&gt;
Ebene K hat 2 &amp;lt;sup&amp;gt;k&amp;lt;/sup&amp;gt; Knoten.&lt;br /&gt;
Falls Tiefe = d , dann N = 2&amp;lt;sup&amp;gt;0&amp;lt;/sup&amp;gt; + 2&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;.....+ 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; = 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1&lt;br /&gt;
&lt;br /&gt;
====Größe des perfect balancierten Baumes====&lt;br /&gt;
Die Tiefe d, kann nicht besser sein als vollständiger Baum&lt;br /&gt;
:'''=&amp;gt;'''N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2 &amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
;schlechteste perfect balancierter Baum : ist ein vollständiger der Tiefe  d - 1 + 1, N &amp;lt;math&amp;gt; \ge&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:'''=&amp;gt;'''  2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1)   &amp;lt;math&amp;gt;~&amp;lt;&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt;)&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
d &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt;  log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; (d + 1)&lt;br /&gt;
&lt;br /&gt;
*Ergibt die Komplexität der Suche im schlechtesten Fall: Anzahl der Vergleiche pro Knoten( = 2 bzw. = 3)&amp;lt;math&amp;gt;\ast&amp;lt;/math&amp;gt;Anzahl der Knoten&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow ~f(N)\le 2d \le 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
*'''perfekt balancierter Baum '''=&amp;gt; AVL-Baum &lt;br /&gt;
*'''balancierter Baum''':&lt;br /&gt;
::&amp;lt;math&amp;gt;\forall  N:d(N)\le c \ast d (N)&amp;lt;/math&amp;gt; und  &amp;lt;math&amp;gt;1 \le c &amp;lt; \mathcal {1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:: d ist die Tiefe von perfekt balancierten Baum&lt;br /&gt;
&lt;br /&gt;
:''Komplexität der Suche'':&amp;lt;math&amp;gt; ~f(N)\le  c\ast 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
:algorithmisch einfacher als perfekt balancierter Baum, aber fast genauso schnell&lt;br /&gt;
&lt;br /&gt;
====Selbst-balancierter Baum====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  def insertTree(node,key):&lt;br /&gt;
    if node is sentinel:       #(None = sentinel)&lt;br /&gt;
        return Node(key)       &lt;br /&gt;
    if node.key == key:&lt;br /&gt;
        return Node&lt;br /&gt;
    if key &amp;lt; node.key:&lt;br /&gt;
        Node = insertTree(node.left, key)&lt;br /&gt;
    else:&lt;br /&gt;
        node.right = insertTree(node.right, key)&lt;br /&gt;
   #optimiere Balance:&lt;br /&gt;
        return node&lt;br /&gt;
&lt;br /&gt;
===Anderson-Bäume===&lt;br /&gt;
&lt;br /&gt;
  class Node:&lt;br /&gt;
    def__init__(self, key):&lt;br /&gt;
        self.key = key&lt;br /&gt;
        self.left = selft.right = None&lt;br /&gt;
        #einfügen:&lt;br /&gt;
        self.level = 1&lt;br /&gt;
&lt;br /&gt;
:''level'' : kodiert Abstand von sentinel&lt;br /&gt;
====Regeln====&lt;br /&gt;
Es gibt vertikale Kanten(parent.level == child.level + 1 ) und horizontale Kanten(parent.level == child.level)&lt;br /&gt;
:*verfeinerter Regel :  ''level kodiert reduzierten Abstand von Setinel''  (d.b. horizontale Kanten werden nicht gezählt)&lt;br /&gt;
::(die nächste zwei Regeln sichern die Balance):&lt;br /&gt;
::*alle RS-Pfade haben die gleiche reduzierte Länge oder ''root'' hat bei allen Pfaden das gleiche Level&lt;br /&gt;
::*kein Pfad hat 2 aufeinanderer folgende horizontale Kanten&lt;br /&gt;
:*nur Kanten zum rechten Kind dürfen horizontal sein&lt;br /&gt;
:*die reduzierte Höhe jedes Blatts ist '''hr=1'''&lt;br /&gt;
&lt;br /&gt;
====Beweis====&lt;br /&gt;
Vereinfachung des Algorithmus&lt;br /&gt;
:'''Satz''':ein Anderson-Baum ist balanciert.&lt;br /&gt;
*1. Sei '''hr'''- die reduzierte Höhe&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; jeder Teilbaum enthält mindestens &amp;lt;math&amp;gt;N\ge 2^{hr} -1 &amp;lt;/math&amp;gt;  Knoten&lt;br /&gt;
&lt;br /&gt;
::a). Blätter : reduzierte Höhe 1 =&amp;gt; &amp;lt;math&amp;gt; N\ge 2^{1} - 1 = 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
::b). inneren Knoten: jeder Unterbaum hat mindestens reduzierte Höhe &amp;lt;math&amp;gt; ~hr - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt;  jeder Unterbaum hat mindestens &amp;lt;math&amp;gt; ~2^{hr} -1 &amp;lt;/math&amp;gt; Knoten&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt;N \ge 2 (2^{hr-1} -1)+1 = 2^w - 2 + 1 = 2^{hr} -1&amp;lt;/math&amp;gt;&lt;br /&gt;
::::alle RS-Pfade heben gleiche Länge&lt;br /&gt;
[[Image:Ander.jpg|450px|right]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
*2. Kein Pfad hat 2 aufeinanderfolgende horizontal Kanten&lt;br /&gt;
:::&amp;lt;math&amp;gt;\Rightarrow  d \le 2 hr + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
*3. zusammen:&lt;br /&gt;
:&amp;lt;math&amp;gt;N \ge 2^{\frac {d}{2}} - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;log_{2}{ N}+1\ge log 2^{\frac {d}{2}} - 1 \ge \frac {d}{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; ~d &amp;lt; 2 log_{2}{(N + 1)}&amp;lt;/math&amp;gt;     - balancierter Baum&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
:'''Suchzeit''' : &amp;lt;math&amp;gt; ~ f(N) = \mathcal{O}(log{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Wie erreicht man die Balance===&lt;br /&gt;
====Rotation====&lt;br /&gt;
[[Image:Abbildung4.jpg|text-top|350px|Illustration von Rotation]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
   def rotateRight (node):&lt;br /&gt;
      root = node.left&lt;br /&gt;
      node.left = node.right&lt;br /&gt;
      root.right = node&lt;br /&gt;
      return root&lt;br /&gt;
   def rotateLeft(node):&lt;br /&gt;
      root = node.right&lt;br /&gt;
      node.right = node.left&lt;br /&gt;
      root.left = node&lt;br /&gt;
      return root&lt;br /&gt;
&lt;br /&gt;
====Optimierung der Balance====&lt;br /&gt;
   &lt;br /&gt;
&lt;br /&gt;
       if node.left is not sentinel and node.level==node.left.level:&lt;br /&gt;
            node &amp;gt; rotateRight(node)&lt;br /&gt;
       if node.right is not sentinel and node.right.right is  not sentinel and node.level==rotate.right.right.level:&lt;br /&gt;
            node = rotateLeft(node)&lt;br /&gt;
            node.level += 1&lt;br /&gt;
[[Image:rotate.jpg|text-top]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
[[Image:liste.png]]&lt;/div&gt;</summary>
		<author><name>Nata</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1517</id>
		<title>Suchen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1517"/>
				<updated>2008-05-29T01:24:25Z</updated>
		
		<summary type="html">&lt;p&gt;Nata: /* Beweis */&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 = sequentialSearch(a, key) &lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq &amp;lt;/math&amp;gt; foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def sequentialSearch(a, key):&lt;br /&gt;
    for i in range(len(a)):&lt;br /&gt;
        if a[i] == key:  # bzw. allgemeiner a[i].key == key &lt;br /&gt;
            return i&lt;br /&gt;
    return -1&lt;br /&gt;
&lt;br /&gt;
Wir wollen jetzt die Komplexität dieses Algorithmus bestimmen, wobei die Problemgröße durch &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt; gegeben ist. &lt;br /&gt;
&lt;br /&gt;
Dabei nimmt man an, dass der innerste Vergleich (a[i] == key) jeweils &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt; ist (diese Annahme könnte verletzt sein, wenn der Vergleichsoperator überladen ist und dadurch eine höhere Komplexität hat). Dieser Vergleich wird in der for-Schleife jeweils N-mal durchgeführt (&amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt;), so dass man nach der Verschachtelungsregel eine gesamte Komplexität von &amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt; erhält.&lt;br /&gt;
&lt;br /&gt;
Der Name ''lineare'' Suche rührt von diesem linearen Anwachsen der Komplexität mit der Arraygröße her.&lt;br /&gt;
&lt;br /&gt;
==Binäre Suche==&lt;br /&gt;
&lt;br /&gt;
Wie wir weiter unten zeigen werden, gestattet es diese Suchmethode, die Gesamtdauer der Suche in großen Datensätzen beträchtlich zu verringern. Die Methode beruht auf dem [http://de.wikipedia.org/wiki/Divide_and_Conquer Divide and Conquer-Prinzip], wobei die Suche in jedem Schritt rekursiv auf eine Hälfte des Datensatzes eingeschränkt wird. Weitere Details zur Methode sind [http://de.wikipedia.org/wiki/Bin%C3%A4re_Suche hier] zu finden. &lt;br /&gt;
&lt;br /&gt;
Die Methode ist nur dann anwendbar beziehungsweise effektiv, wenn folgendes gilt:&lt;br /&gt;
&lt;br /&gt;
# Auf der Eigenschaft der Daten, die zur Suche verwendet wird, ist eine Ordnung im Sinne von &amp;lt; oder &amp;gt; definiert.&lt;br /&gt;
# Wir wollen uns auf Datensätze beschränken, die schon fertig aufgebaut sind, in die also keine neuen Elemente mehr eingefügt werden, wenn man mit dem Suchen beginnt. Ist dies nicht der Fall, so ist unter Umständen die Implementierung über einen [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum Binärbaum] (siehe auch weiter unten) geschickter. &lt;br /&gt;
&lt;br /&gt;
Der folgende Algorithmus zeigt eine beispielhafte Implementierung der Methode (wir setzen wieder &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt;):&lt;br /&gt;
&lt;br /&gt;
 a = [...,...]     # array&lt;br /&gt;
 a.sort()   # sortiere über Ordnung des Schlüssels&lt;br /&gt;
 foundIndex = binSearch(a, key, 0, len(a))  # (Array, Schlüssel, von wo bis wo suchen im Array)&lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq&amp;lt;/math&amp;gt;  foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def binSearch(a, key, start, end):  # start ist 1. Index, end ist letzter Index + 1&lt;br /&gt;
    size = end - start   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if size &amp;lt;= 0:   # Bereich leer?  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return -1   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    center = (start + end)/2   # Integer Division, Ergebnis wird abgerundet, wichtig für ganzzahlige Indizes &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if a[center] == key:  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return center  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    elif a[center] &amp;lt; key:  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return binSearch(a, key, center + 1, end)  # Rekursion&lt;br /&gt;
    else:&lt;br /&gt;
        return binSearch(a, key, start + 1, center)  # Rekursion&lt;br /&gt;
&lt;br /&gt;
Zur Berechnung der Komplexität dieses Algorithmus vernachlässigen wir zunächst den Aufwand, den die Sortierung weiter oben verursacht. Dieser Schritt mag oder mag nicht zulässig sein.&lt;br /&gt;
&lt;br /&gt;
Nach der Sequenzregel haben auch alle &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;.  Es bleibt die Komplexität der Rekursion zu berechnen. Die gesamte Komplexität des Algorithmus (jetzt als Funktion f bezeichnet) setzt sich zusammen aus den oben erwähnten &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen sowie der Rekursion und ist &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f(N) = \mathcal{O}(1) + f(N/2) = \mathcal{O}(1) + \mathcal{O}(1) + f(N/4) = ... = \underbrace{\mathcal{O}(1) + ... + \mathcal{O}(1) + \underbrace{f(0)}_{\mathcal{O}(1)\, \rightarrow \,\mathrm{size-Abfrage}}}_{n+1 \,\mathrm{Terme}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Falls jetzt gilt &amp;lt;math&amp;gt; N = 2^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \rightarrow f(N) = \mathcal{O}(1) \cdot \mathcal{O}(n+1) = \mathcal{O}(n) = \mathcal{O}(\lg N) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Für große Datenmengen ist die ''binäre Suche'' also weit effizienter als die ''lineare Suche''. Verdoppelt sich beispielsweise die zu durchsuchende Datenmenge, so verdoppelt sich der Aufwand für die ''sequentielle Suche'' - bei der ''binären Suche'' hingegen benötigt man lediglich eine zusätzliche Vergleichsoperation. &lt;br /&gt;
&lt;br /&gt;
Für kleine Daten (&amp;lt;math&amp;gt; N = 4,\, 5 &amp;lt;/math&amp;gt;) ist die ''sequentielle Suche'' jedoch schneller als die ''binäre Suche'', da hier die rekursiven Funktionsaufrufe teurer als das Mehr an Vergleichen sind. Ein anderer ungünstiger Fall ist gegeben, wenn nur sehr wenige Suchanfragen erfolgen (weniger als &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; viele). Dann wird der Aufwand durch das Sortieren des Arrays dominiert, ist also &amp;lt;math&amp;gt;\mathcal{O}(N \lg N) &amp;lt;/math&amp;gt;. Auch dann ist sequentielle Suche vorzuziehen.&lt;br /&gt;
&lt;br /&gt;
Eine relativ einfache Möglichkeit, die ''binäre Suche'' zu verbessern, ist die sogenannte ''Interpolationssuche''. Hierbei wird die neue Position für die Suche, also die Mitte des Arrays, durch eine Schätzung ersetzt, die angibt, wo sich der Schlüssel innerhalb des Arrays befinden könnte. Bei der Suche in einem Telefonbuch nach dem Namen Zebra würde man ja auch nicht in der Mitte anfangen. Näheres hierzu im Buch von ''Sedgewick''.&lt;br /&gt;
&lt;br /&gt;
Um sich den Algorithmus der ''binären Suche'' klar zu machen, ist es instruktiv, sich die folgende Tabelle genauer anzusehen, die die sukzessive Belegung der Variablen bei verschiedenen Anfragen beschreibt. Die Testfälle wurden nach dem Prinzip des ''domain partitioning'' gewählt. Das zugehörige Array hat die Einträge&lt;br /&gt;
&lt;br /&gt;
 a = [2, 3, 4, 5, 6]&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align:center&amp;quot; border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;5&amp;quot; cellspacing=&amp;quot;0&amp;quot; &lt;br /&gt;
! gesuchter key   !!  start      !! end  !! size !! center !! return &amp;lt;br/&amp;gt; (-1 oder index)  !! Kommentare  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 4     ||0            || 5    ||  5   || 2   ||  2         || gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 2     || 0           || 5    ||  5   || 2   ||            || linker Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||           &lt;br /&gt;
|-&lt;br /&gt;
|       ||  0          || 1    ||  1   || 0   ||  0         || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 1     ||0            || 5    ||  5   || 2   ||            || links außerhalb&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 1    ||  1   || 0   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 0    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 6     ||0            || 5    ||  5   || 2   ||            || rechter Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    || 2    || 4   || 4          || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 5     ||0            || 5    ||  5   || 2   ||            || typischer Fall&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||3            || 5    ||  2   || 4   ||            ||  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       || 3           || 4    ||  1   || 3   || 3          || gefunden&lt;br /&gt;
|- &lt;br /&gt;
| 7     ||0            || 5    ||  5   || 2   ||            || rechts außerhalb        &lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    ||  2   || 4   ||            ||&lt;br /&gt;
|-&lt;br /&gt;
|       ||5            || 5    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Suche in einem Binärbaum ==&lt;br /&gt;
&lt;br /&gt;
Eine kurze Einführung in Binärbäume findet man [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum hier].&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum.png|text-top|300x300px|Zur Illustration von Bäumen]]&lt;br /&gt;
&lt;br /&gt;
Bäume sind zweidimensional verkettete Strukturen. Sie gehören zu den fundamentalen Datenstrukturen in der Informatik. Da man in Bäumen nicht nur Daten speichern kann, sondern auch relevante Beziehungen der Daten untereinander, festgelegt über eine Ordnung auf der vergleichenden Dateneigenschaft (''Schlüssel''), eignen sich Bäume also insbesondere, um gesuchte Daten schnell wieder auffinden zu können.&lt;br /&gt;
&lt;br /&gt;
Ein ''Binärbaum'' wie oben skizziert besteht aus einer Menge von ''Knoten'', die untereinander durch ''Kanten'' verbunden sind. Jeder Knoten hat einen linken und einen rechten Unterbaum, der auch leer sein kann (in Python ließe sich dies mit ''None'' implementieren). Führt eine Kante von Knoten A zu Knoten B, so heißt A Vater von B und B Kind von A. Es gibt genau einen Knoten ohne Vater, den man ''Wurzel'' nennt. Knoten ohne Kinder heißen ''Blätter''.&lt;br /&gt;
&lt;br /&gt;
Ein ''Suchbaum'' hat zusätzlich die Eigenschaft, dass die Schlüssel jedes Knotens sortiert sind. Alle Schlüssel im linken Unterbaum sind kleiner, alle Schlüssel im rechten Unterbaum sind größer als ihr Vater. Wir wollen hierbei annehmen, dass jeder Schlüssel pro Datensatz nur einmal vorkommt, da sich sonst die &amp;gt;- oder &amp;lt;-Relation nicht mehr strikt erfüllen ließe.&lt;br /&gt;
&lt;br /&gt;
Um in einem Baum suchen zu können, wollen wir von zwei Annahmen ausgehen:&lt;br /&gt;
# Einfügen und Suchen im Baum wechseln sich ab. (Wenn das Suchen erst beginnt, nachdem alle Einfügungen erfolgt sind, wäre ein dynamisches Array mit binärer Suche wie oben wesentlich einfacher.)&lt;br /&gt;
# Der Schlüssel, der die Anordnung bestimmt, kennt eine [http://de.wikipedia.org/wiki/Ordnungsrelation Ordnung] (&amp;lt;-Relation oder &amp;gt;-Relation).&lt;br /&gt;
&lt;br /&gt;
Der folgende Python-Code zeigt beispielhaft, wie man in einem Suchbaum suchen könnte. Der Konstruktor für einen Knoten des Suchbaums ließe sich zum Beispiel so implementieren:&lt;br /&gt;
 &lt;br /&gt;
 class Node:&lt;br /&gt;
     def __init__(self, key):&lt;br /&gt;
         self.key = key&lt;br /&gt;
         self.left = self.right = None&lt;br /&gt;
 &lt;br /&gt;
 root = ...    # Wurzel des Suchbaums&lt;br /&gt;
 nodeFound = treeSearch(root, key)   # None, falls nichts gefunden&lt;br /&gt;
 &lt;br /&gt;
 def treeSearch(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return None&lt;br /&gt;
     elif node.key == key:&lt;br /&gt;
         return node&lt;br /&gt;
     elif key &amp;lt; node.key:&lt;br /&gt;
         return treeSearch(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         return treeSearch(node.right, key)&lt;br /&gt;
&lt;br /&gt;
Daraus resultiert der folgende Suchalgorithmus:&lt;br /&gt;
&lt;br /&gt;
 def treeSort(node,array):     # dynamisches Array als 2. Argument&lt;br /&gt;
     if node is None:     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
         return None:&lt;br /&gt;
     treeSort(node.left, array)     # rekursiv&lt;br /&gt;
     array.append(node.key)     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
     treeSort(node.right, array)     # rekursiv&lt;br /&gt;
&lt;br /&gt;
Komplexität:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 f(N)=\mathcal{O}(1)+f(N_\mathrm{left})+f(N_\mathrm{right})=\mathcal{O}(1)+\mathcal{O}(1)+f(N_\mathrm{leftleft})+f(N_\mathrm{leftright})+\mathcal{O}(1)+f(N_\mathrm{rightleft})&lt;br /&gt;
 +f(N_\mathrm{leftright})=N\ast\mathcal{O}(1)=\mathcal{O}(N)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sortier-Pseudocode:&lt;br /&gt;
&lt;br /&gt;
 Sortieren:&lt;br /&gt;
     (Array) a     # unsortiert&lt;br /&gt;
     (tree) t     # zunächst leer&lt;br /&gt;
 (dynamisches Array) r     # später sortiert&lt;br /&gt;
 for e in a:&lt;br /&gt;
     t = treeInsert(t, e)&lt;br /&gt;
 treeSort(t, r)&lt;br /&gt;
&lt;br /&gt;
== Insert ==&lt;br /&gt;
&lt;br /&gt;
Was passiert wenn der key (Schlüssel) schon vorhanden ist?&lt;br /&gt;
* error (exception)&lt;br /&gt;
* nichts einfügen&lt;br /&gt;
* nichts einfügen aber einen boolean return zurückgeben (einfügen=true, false)&lt;br /&gt;
* nochmals eingefügt (z.B. in der Node Klasse)&lt;br /&gt;
&lt;br /&gt;
Wobei die ersten 3 Punkte zur Mengensemantik gehören und der letzte eine Multimenge ist.&lt;br /&gt;
&lt;br /&gt;
Algorithmus im zweiten Fall (nichts einfügen):&lt;br /&gt;
&lt;br /&gt;
 def treeInsert(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return Node(key)     # Alternative Schreibweise: node = Node(key)&lt;br /&gt;
     if node.key == key:      # und dann:&lt;br /&gt;
         return node          # pass&lt;br /&gt;
     elif key &amp;lt; node.key:     # links im Baum&lt;br /&gt;
         node.left = treeInsert(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         node.right = treeInsert(node.right, key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Remove ==&lt;br /&gt;
Fälle:&lt;br /&gt;
# key (bzw. Knoten der key enthält) ist ein Blatt =&amp;gt; einfach löschen&lt;br /&gt;
# node hat &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; linken Unterbaum oder &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; rechten Unterbaum =&amp;gt; durch Unterbaum ersetzen&lt;br /&gt;
# node hat beide Unterbäume:&lt;br /&gt;
#* Suche Vorgänger: max k &amp;lt; key (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; keys); Vorgänger ist immer ein Fall 1 oder Fall 2&lt;br /&gt;
#*:=&amp;gt; ersetze node durch Vorgänger und entferne Vorgänger&lt;br /&gt;
&lt;br /&gt;
 def treePredecessor(node):     # wird nur bei Fall 3 aufgerufen&lt;br /&gt;
     node = node.left&lt;br /&gt;
     while node.right is not None:&lt;br /&gt;
         node = node.right&lt;br /&gt;
     return node&lt;br /&gt;
 &lt;br /&gt;
 def treeRemove(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return node&lt;br /&gt;
     if key &amp;lt; node.key:&lt;br /&gt;
         node.left = treeRemove(node.left, key)&lt;br /&gt;
     elif key &amp;gt; node.key:&lt;br /&gt;
         node.right = treeRemove(node.right, key)&lt;br /&gt;
     else:&lt;br /&gt;
         if node.left is None and node.right is None:     # Fall 1&lt;br /&gt;
             node = None&lt;br /&gt;
         elif node.left is None:     # Fall 2&lt;br /&gt;
             node = node.right       # +&lt;br /&gt;
         elif node.right is None:    # Fall 2&lt;br /&gt;
             node = node.left&lt;br /&gt;
         else:&lt;br /&gt;
             pred = treePredecessor(node)&lt;br /&gt;
             node.key = pred.key&lt;br /&gt;
             node.left = treeRemove(node.left, pred.key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Komplexitätsanalyse ==&lt;br /&gt;
&lt;br /&gt;
* Pfad (Zwischen node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;): &lt;br /&gt;
**Folge von Knoten (node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt;,...,node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt;), sodass:&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;ki&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;ki+1&amp;lt;/sub&amp;gt; haben eine gemeinsame Kante.&lt;br /&gt;
[[Image:Baum_Pfad.png]]&lt;br /&gt;
(Ein Baum ist ein Graph, indem es zwischen beliebigen Knoten stets genau einen Pfad gibt.)&lt;br /&gt;
&lt;br /&gt;
* Länge eines Pfades: Anzahl der Kanten = Anzahl der Knoten - 1&lt;br /&gt;
* Tiefe eines Knotens: Pfadlänge vom Knoten zur Wurzel des Baumes.&lt;br /&gt;
* Tiefe des Baumes: maximale Tiefe eines Knotens&lt;br /&gt;
* Balance eines Baumes: maximale Tiefe(k) - minimale Tiefe(k) (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; Blätter)&lt;br /&gt;
&lt;br /&gt;
== Ungünstigster Fall von treeSearch ==&lt;br /&gt;
&lt;br /&gt;
Komplexität von treeSearch = Länge des Pfades zum Knoten wo &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; gefunden wird, oder erkannt wird, dass &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; nicht im Baum ist.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; wird nicht gefunden, aber für diese Entscheidung muss der Längste Pfad vollständig durchlaufen werden.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(T)&amp;lt;/math&amp;gt; wo T = Tiefe des Baumes&lt;br /&gt;
*: [1,2,3,4,5]:&amp;lt;br /&amp;gt; [[Image:Balance.png]]&lt;br /&gt;
*: Tiefe T = 4, Balance = 4&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; wo N = Anzahl der Elemente.&lt;br /&gt;
&lt;br /&gt;
== Aufgabe ==&lt;br /&gt;
&lt;br /&gt;
Minimiere Balance (erzeuge balancierten Baum):&lt;br /&gt;
# Einfügen in geschickter Reihenfolge (siehe Übungsaufgabe)&lt;br /&gt;
# Selbstbalancierter Baum:&lt;br /&gt;
#* Überprüfen der Balance nach jedem Einfügen&lt;br /&gt;
#* Umstrukturieren des Baumes, falls Balance &amp;gt; 1 (Suchbaum-Bedingung muss erhalten bleiben)&lt;br /&gt;
#* AVL-Bäume (älteste Variante)&lt;br /&gt;
#* Rot-Schwarz-Bäume (verbreiteste Variante)&lt;br /&gt;
#* Treaps (flexibelste Variante, siehe Übung)&lt;br /&gt;
#* Splay trees&lt;br /&gt;
#* Andersson Trees (einfachste Variante)&lt;br /&gt;
(#* Skip Lists (schnellste Variante, aber kein Binärbaum))&lt;br /&gt;
&lt;br /&gt;
== Umstrukturieren, so dass Suchbaumbedingung erhalten bleibt: ==&lt;br /&gt;
&lt;br /&gt;
Rotation: elementare Umstrukturierungen&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum_Rotation.png]]&lt;br /&gt;
&lt;br /&gt;
 def rotateRight(node):&lt;br /&gt;
     newRoot = node.left&lt;br /&gt;
     node.left = newRoot.right&lt;br /&gt;
     newRoot.right = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
 def rotateLeft(node):&lt;br /&gt;
     newRoot = node.right&lt;br /&gt;
     node.right = newRoot.left&lt;br /&gt;
     newRoot.left = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
== Balance eines Suchbaumes ==&lt;br /&gt;
&lt;br /&gt;
===Balance eines Baumes zu  definieren:===&lt;br /&gt;
[[Image:Abbildung1.jpg|250px|right]]&lt;br /&gt;
*betrachte &amp;lt;tt&amp;gt;None&amp;lt;/tt&amp;gt; als zusätzlichen Knoten : '''sentinel'''   ([[englische Sprache|engl.]] für ''Wächter'')&lt;br /&gt;
*oder defeniere speziellen sentinel-Knoten&lt;br /&gt;
*RS - Pfades : von &amp;lt;tt&amp;gt;root&amp;lt;/tt&amp;gt; &amp;amp;rarr; ''sentinel''&lt;br /&gt;
*Länge eines Pfades ''P''      '''''|P|'''''&lt;br /&gt;
* &amp;lt;u&amp;gt;die Balance eines Baumes zu defenieren:&amp;lt;/u&amp;gt;&lt;br /&gt;
: '''''B = max P '''''&amp;lt;math&amp;gt; \in &amp;lt;/math&amp;gt; '''''{RS}    |P| - min |P|'''''&lt;br /&gt;
:'''''{RS}'''''  Menge aller RS-Pfade&lt;br /&gt;
;vollständiger Baum:  Balance &amp;lt;math&amp;gt;~=0&amp;lt;/math&amp;gt;&lt;br /&gt;
:Alle Knoten(außer Blättern) haben 2 Kinder.&lt;br /&gt;
;perfect balancierter Baum:  Balance  &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 1&lt;br /&gt;
'''alternative Defenition für perfect balancierte Bäume'''&lt;br /&gt;
:Für jeden Teilbaum gilt es: rechtes und linkes Kind (für jeden Knoten) sind auch wieder perfect balancierte Bäume und ihre Höhe unterscheidet sich höchstens um '''1'''.&lt;br /&gt;
&lt;br /&gt;
===Größe===&lt;br /&gt;
[[Image:baum-v.png|700px|right]]&lt;br /&gt;
====Größe des vollständigen Baumes====&lt;br /&gt;
Ebene K hat 2 &amp;lt;sup&amp;gt;k&amp;lt;/sup&amp;gt; Knoten.&lt;br /&gt;
Falls Tiefe = d , dann N = 2&amp;lt;sup&amp;gt;0&amp;lt;/sup&amp;gt; + 2&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;.....+ 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; = 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1&lt;br /&gt;
&lt;br /&gt;
====Größe des perfect balancierten Baumes====&lt;br /&gt;
Die Tiefe d, kann nicht besser sein als vollständiger Baum&lt;br /&gt;
:'''=&amp;gt;'''N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2 &amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
;schlechteste perfect balancierter Baum : ist ein vollständiger der Tiefe  d - 1 + 1, N &amp;lt;math&amp;gt; \ge&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:'''=&amp;gt;'''  2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1)   &amp;lt;math&amp;gt;~&amp;lt;&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt;)&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
d &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt;  log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; (d + 1)&lt;br /&gt;
&lt;br /&gt;
*Ergibt die Komplexität der Suche im schlechtesten Fall: Anzahl der Vergleiche pro Knoten( = 2 bzw. = 3)&amp;lt;math&amp;gt;\ast&amp;lt;/math&amp;gt;Anzahl der Knoten&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow ~f(N)\le 2d \le 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
*'''perfekt balancierter Baum '''=&amp;gt; AVL-Baum &lt;br /&gt;
*'''balancierter Baum''':&lt;br /&gt;
::&amp;lt;math&amp;gt;\forall  N:d(N)\le c \ast d (N)&amp;lt;/math&amp;gt; und  &amp;lt;math&amp;gt;1 \le c &amp;lt; \mathcal {1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:: d ist die Tiefe von perfekt balancierten Baum&lt;br /&gt;
&lt;br /&gt;
:''Komplexität der Suche'':&amp;lt;math&amp;gt; ~f(N)\le  c\ast 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
:algorithmisch einfacher als perfekt balancierter Baum, aber fast genauso schnell&lt;br /&gt;
&lt;br /&gt;
====Selbst-balancierter Baum====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  def insertTree(node,key):&lt;br /&gt;
    if node is sentinel:       #(None = sentinel)&lt;br /&gt;
        return Node(key)       &lt;br /&gt;
    if node.key == key:&lt;br /&gt;
        return Node&lt;br /&gt;
    if key &amp;lt; node.key:&lt;br /&gt;
        Node = insertTree(node.left, key)&lt;br /&gt;
    else:&lt;br /&gt;
        node.right = insertTree(node.right, key)&lt;br /&gt;
   #optimiere Balance:&lt;br /&gt;
        return node&lt;br /&gt;
&lt;br /&gt;
===Anderson-Bäume===&lt;br /&gt;
&lt;br /&gt;
  class Node:&lt;br /&gt;
    def__init__(self, key):&lt;br /&gt;
        self.key = key&lt;br /&gt;
        self.left = selft.right = None&lt;br /&gt;
        #einfügen:&lt;br /&gt;
        self.level = 1&lt;br /&gt;
&lt;br /&gt;
:''level'' : kodiert Abstand von sentinel&lt;br /&gt;
====Regeln====&lt;br /&gt;
Es gibt vertikale Kanten(parent.level == child.level + 1 ) und horizontale Kanten(parent.level == child.level)&lt;br /&gt;
:*verfeinerter Regel :  ''level kodiert reduzierten Abstand von Setinel''  (d.b. horizontale Kanten werden nicht gezählt)&lt;br /&gt;
::(die nächste zwei Regeln sichern die Balance):&lt;br /&gt;
::*alle RS-Pfade haben die gleiche reduzierte Länge oder ''root'' hat bei allen Pfaden das gleiche Level&lt;br /&gt;
::*kein Pfad hat 2 aufeinanderer folgende horizontale Kanten&lt;br /&gt;
:*nur Kanten zum rechten Kind dürfen horizontal sein&lt;br /&gt;
:*die reduzierte Höhe jedes Blatts ist '''hr=1'''&lt;br /&gt;
&lt;br /&gt;
====Beweis====&lt;br /&gt;
Vereinfachung des Algorithmus&lt;br /&gt;
:'''Satz''':ein Anderson-Baum ist balanciert.&lt;br /&gt;
*1. Sei '''hr'''- die reduzierte Höhe&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; jeder Teilbaum enthält mindestens &amp;lt;math&amp;gt;N\ge 2^{hr} -1 &amp;lt;/math&amp;gt;  Knoten&lt;br /&gt;
&lt;br /&gt;
::a). Blätter : reduzierte Höhe 1 =&amp;gt; &amp;lt;math&amp;gt; N\ge 2^{1} - 1 = 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
::b). inneren Knoten: jeder Unterbaum hat mindestens reduzierte Höhe &amp;lt;math&amp;gt; ~hr - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt;  jeder Unterbaum hat mindestens &amp;lt;math&amp;gt; ~2^{hr} -1 &amp;lt;/math&amp;gt; Knoten&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt;N \ge 2 (2^{hr-1} -1)+1 = 2^w - 2 + 1 = 2^{hr} -1&amp;lt;/math&amp;gt;&lt;br /&gt;
::::alle RS-Pfade heben gleiche Länge&lt;br /&gt;
[[Image:Ander.jpg|400px|right]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
*2. Kein Pfad hat 2 aufeinanderfolgende horizontal Kanten&lt;br /&gt;
:::&amp;lt;math&amp;gt;\Rightarrow  d \le 2 hr + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
*3. zusammen:&lt;br /&gt;
:&amp;lt;math&amp;gt;N \ge 2^{\frac {d}{2}} - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;log_{2}{ N}+1\ge log 2^{\frac {d}{2}} - 1 \ge \frac {d}{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; ~d &amp;lt; 2 log_{2}{(N + 1)}&amp;lt;/math&amp;gt;     - balancierter Baum&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
:'''Suchzeit''' : &amp;lt;math&amp;gt; ~ f(N) = \mathcal{O}(log{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Wie erreicht man die Balance===&lt;br /&gt;
====Rotation====&lt;br /&gt;
[[Image:Abbildung4.jpg|text-top|350px|Illustration von Rotation]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
   def rotateRight (node):&lt;br /&gt;
      root = node.left&lt;br /&gt;
      node.left = node.right&lt;br /&gt;
      root.right = node&lt;br /&gt;
      return root&lt;br /&gt;
   def rotateLeft(node):&lt;br /&gt;
      root = node.right&lt;br /&gt;
      node.right = node.left&lt;br /&gt;
      root.left = node&lt;br /&gt;
      return root&lt;br /&gt;
&lt;br /&gt;
====Optimierung der Balance====&lt;br /&gt;
   &lt;br /&gt;
&lt;br /&gt;
       if node.left is not sentinel and node.level==node.left.level:&lt;br /&gt;
            node &amp;gt; rotateRight(node)&lt;br /&gt;
       if node.right is not sentinel and node.right.right is  not sentinel and node.level==rotate.right.right.level:&lt;br /&gt;
            node = rotateLeft(node)&lt;br /&gt;
            node.level += 1&lt;br /&gt;
[[Image:rotate.jpg|text-top]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
[[Image:liste.png]]&lt;/div&gt;</summary>
		<author><name>Nata</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=File:Ander.jpg&amp;diff=1516</id>
		<title>File:Ander.jpg</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=File:Ander.jpg&amp;diff=1516"/>
				<updated>2008-05-29T01:21:25Z</updated>
		
		<summary type="html">&lt;p&gt;Nata: uploaded a new version of &amp;quot;Image:Ander.jpg&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Nata</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1515</id>
		<title>Suchen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1515"/>
				<updated>2008-05-29T01:17:21Z</updated>
		
		<summary type="html">&lt;p&gt;Nata: /* Beweis */&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 = sequentialSearch(a, key) &lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq &amp;lt;/math&amp;gt; foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def sequentialSearch(a, key):&lt;br /&gt;
    for i in range(len(a)):&lt;br /&gt;
        if a[i] == key:  # bzw. allgemeiner a[i].key == key &lt;br /&gt;
            return i&lt;br /&gt;
    return -1&lt;br /&gt;
&lt;br /&gt;
Wir wollen jetzt die Komplexität dieses Algorithmus bestimmen, wobei die Problemgröße durch &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt; gegeben ist. &lt;br /&gt;
&lt;br /&gt;
Dabei nimmt man an, dass der innerste Vergleich (a[i] == key) jeweils &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt; ist (diese Annahme könnte verletzt sein, wenn der Vergleichsoperator überladen ist und dadurch eine höhere Komplexität hat). Dieser Vergleich wird in der for-Schleife jeweils N-mal durchgeführt (&amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt;), so dass man nach der Verschachtelungsregel eine gesamte Komplexität von &amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt; erhält.&lt;br /&gt;
&lt;br /&gt;
Der Name ''lineare'' Suche rührt von diesem linearen Anwachsen der Komplexität mit der Arraygröße her.&lt;br /&gt;
&lt;br /&gt;
==Binäre Suche==&lt;br /&gt;
&lt;br /&gt;
Wie wir weiter unten zeigen werden, gestattet es diese Suchmethode, die Gesamtdauer der Suche in großen Datensätzen beträchtlich zu verringern. Die Methode beruht auf dem [http://de.wikipedia.org/wiki/Divide_and_Conquer Divide and Conquer-Prinzip], wobei die Suche in jedem Schritt rekursiv auf eine Hälfte des Datensatzes eingeschränkt wird. Weitere Details zur Methode sind [http://de.wikipedia.org/wiki/Bin%C3%A4re_Suche hier] zu finden. &lt;br /&gt;
&lt;br /&gt;
Die Methode ist nur dann anwendbar beziehungsweise effektiv, wenn folgendes gilt:&lt;br /&gt;
&lt;br /&gt;
# Auf der Eigenschaft der Daten, die zur Suche verwendet wird, ist eine Ordnung im Sinne von &amp;lt; oder &amp;gt; definiert.&lt;br /&gt;
# Wir wollen uns auf Datensätze beschränken, die schon fertig aufgebaut sind, in die also keine neuen Elemente mehr eingefügt werden, wenn man mit dem Suchen beginnt. Ist dies nicht der Fall, so ist unter Umständen die Implementierung über einen [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum Binärbaum] (siehe auch weiter unten) geschickter. &lt;br /&gt;
&lt;br /&gt;
Der folgende Algorithmus zeigt eine beispielhafte Implementierung der Methode (wir setzen wieder &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt;):&lt;br /&gt;
&lt;br /&gt;
 a = [...,...]     # array&lt;br /&gt;
 a.sort()   # sortiere über Ordnung des Schlüssels&lt;br /&gt;
 foundIndex = binSearch(a, key, 0, len(a))  # (Array, Schlüssel, von wo bis wo suchen im Array)&lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq&amp;lt;/math&amp;gt;  foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def binSearch(a, key, start, end):  # start ist 1. Index, end ist letzter Index + 1&lt;br /&gt;
    size = end - start   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if size &amp;lt;= 0:   # Bereich leer?  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return -1   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    center = (start + end)/2   # Integer Division, Ergebnis wird abgerundet, wichtig für ganzzahlige Indizes &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if a[center] == key:  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return center  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    elif a[center] &amp;lt; key:  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return binSearch(a, key, center + 1, end)  # Rekursion&lt;br /&gt;
    else:&lt;br /&gt;
        return binSearch(a, key, start + 1, center)  # Rekursion&lt;br /&gt;
&lt;br /&gt;
Zur Berechnung der Komplexität dieses Algorithmus vernachlässigen wir zunächst den Aufwand, den die Sortierung weiter oben verursacht. Dieser Schritt mag oder mag nicht zulässig sein.&lt;br /&gt;
&lt;br /&gt;
Nach der Sequenzregel haben auch alle &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;.  Es bleibt die Komplexität der Rekursion zu berechnen. Die gesamte Komplexität des Algorithmus (jetzt als Funktion f bezeichnet) setzt sich zusammen aus den oben erwähnten &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen sowie der Rekursion und ist &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f(N) = \mathcal{O}(1) + f(N/2) = \mathcal{O}(1) + \mathcal{O}(1) + f(N/4) = ... = \underbrace{\mathcal{O}(1) + ... + \mathcal{O}(1) + \underbrace{f(0)}_{\mathcal{O}(1)\, \rightarrow \,\mathrm{size-Abfrage}}}_{n+1 \,\mathrm{Terme}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Falls jetzt gilt &amp;lt;math&amp;gt; N = 2^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \rightarrow f(N) = \mathcal{O}(1) \cdot \mathcal{O}(n+1) = \mathcal{O}(n) = \mathcal{O}(\lg N) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Für große Datenmengen ist die ''binäre Suche'' also weit effizienter als die ''lineare Suche''. Verdoppelt sich beispielsweise die zu durchsuchende Datenmenge, so verdoppelt sich der Aufwand für die ''sequentielle Suche'' - bei der ''binären Suche'' hingegen benötigt man lediglich eine zusätzliche Vergleichsoperation. &lt;br /&gt;
&lt;br /&gt;
Für kleine Daten (&amp;lt;math&amp;gt; N = 4,\, 5 &amp;lt;/math&amp;gt;) ist die ''sequentielle Suche'' jedoch schneller als die ''binäre Suche'', da hier die rekursiven Funktionsaufrufe teurer als das Mehr an Vergleichen sind. Ein anderer ungünstiger Fall ist gegeben, wenn nur sehr wenige Suchanfragen erfolgen (weniger als &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; viele). Dann wird der Aufwand durch das Sortieren des Arrays dominiert, ist also &amp;lt;math&amp;gt;\mathcal{O}(N \lg N) &amp;lt;/math&amp;gt;. Auch dann ist sequentielle Suche vorzuziehen.&lt;br /&gt;
&lt;br /&gt;
Eine relativ einfache Möglichkeit, die ''binäre Suche'' zu verbessern, ist die sogenannte ''Interpolationssuche''. Hierbei wird die neue Position für die Suche, also die Mitte des Arrays, durch eine Schätzung ersetzt, die angibt, wo sich der Schlüssel innerhalb des Arrays befinden könnte. Bei der Suche in einem Telefonbuch nach dem Namen Zebra würde man ja auch nicht in der Mitte anfangen. Näheres hierzu im Buch von ''Sedgewick''.&lt;br /&gt;
&lt;br /&gt;
Um sich den Algorithmus der ''binären Suche'' klar zu machen, ist es instruktiv, sich die folgende Tabelle genauer anzusehen, die die sukzessive Belegung der Variablen bei verschiedenen Anfragen beschreibt. Die Testfälle wurden nach dem Prinzip des ''domain partitioning'' gewählt. Das zugehörige Array hat die Einträge&lt;br /&gt;
&lt;br /&gt;
 a = [2, 3, 4, 5, 6]&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align:center&amp;quot; border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;5&amp;quot; cellspacing=&amp;quot;0&amp;quot; &lt;br /&gt;
! gesuchter key   !!  start      !! end  !! size !! center !! return &amp;lt;br/&amp;gt; (-1 oder index)  !! Kommentare  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 4     ||0            || 5    ||  5   || 2   ||  2         || gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 2     || 0           || 5    ||  5   || 2   ||            || linker Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||           &lt;br /&gt;
|-&lt;br /&gt;
|       ||  0          || 1    ||  1   || 0   ||  0         || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 1     ||0            || 5    ||  5   || 2   ||            || links außerhalb&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 1    ||  1   || 0   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 0    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 6     ||0            || 5    ||  5   || 2   ||            || rechter Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    || 2    || 4   || 4          || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 5     ||0            || 5    ||  5   || 2   ||            || typischer Fall&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||3            || 5    ||  2   || 4   ||            ||  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       || 3           || 4    ||  1   || 3   || 3          || gefunden&lt;br /&gt;
|- &lt;br /&gt;
| 7     ||0            || 5    ||  5   || 2   ||            || rechts außerhalb        &lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    ||  2   || 4   ||            ||&lt;br /&gt;
|-&lt;br /&gt;
|       ||5            || 5    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Suche in einem Binärbaum ==&lt;br /&gt;
&lt;br /&gt;
Eine kurze Einführung in Binärbäume findet man [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum hier].&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum.png|text-top|300x300px|Zur Illustration von Bäumen]]&lt;br /&gt;
&lt;br /&gt;
Bäume sind zweidimensional verkettete Strukturen. Sie gehören zu den fundamentalen Datenstrukturen in der Informatik. Da man in Bäumen nicht nur Daten speichern kann, sondern auch relevante Beziehungen der Daten untereinander, festgelegt über eine Ordnung auf der vergleichenden Dateneigenschaft (''Schlüssel''), eignen sich Bäume also insbesondere, um gesuchte Daten schnell wieder auffinden zu können.&lt;br /&gt;
&lt;br /&gt;
Ein ''Binärbaum'' wie oben skizziert besteht aus einer Menge von ''Knoten'', die untereinander durch ''Kanten'' verbunden sind. Jeder Knoten hat einen linken und einen rechten Unterbaum, der auch leer sein kann (in Python ließe sich dies mit ''None'' implementieren). Führt eine Kante von Knoten A zu Knoten B, so heißt A Vater von B und B Kind von A. Es gibt genau einen Knoten ohne Vater, den man ''Wurzel'' nennt. Knoten ohne Kinder heißen ''Blätter''.&lt;br /&gt;
&lt;br /&gt;
Ein ''Suchbaum'' hat zusätzlich die Eigenschaft, dass die Schlüssel jedes Knotens sortiert sind. Alle Schlüssel im linken Unterbaum sind kleiner, alle Schlüssel im rechten Unterbaum sind größer als ihr Vater. Wir wollen hierbei annehmen, dass jeder Schlüssel pro Datensatz nur einmal vorkommt, da sich sonst die &amp;gt;- oder &amp;lt;-Relation nicht mehr strikt erfüllen ließe.&lt;br /&gt;
&lt;br /&gt;
Um in einem Baum suchen zu können, wollen wir von zwei Annahmen ausgehen:&lt;br /&gt;
# Einfügen und Suchen im Baum wechseln sich ab. (Wenn das Suchen erst beginnt, nachdem alle Einfügungen erfolgt sind, wäre ein dynamisches Array mit binärer Suche wie oben wesentlich einfacher.)&lt;br /&gt;
# Der Schlüssel, der die Anordnung bestimmt, kennt eine [http://de.wikipedia.org/wiki/Ordnungsrelation Ordnung] (&amp;lt;-Relation oder &amp;gt;-Relation).&lt;br /&gt;
&lt;br /&gt;
Der folgende Python-Code zeigt beispielhaft, wie man in einem Suchbaum suchen könnte. Der Konstruktor für einen Knoten des Suchbaums ließe sich zum Beispiel so implementieren:&lt;br /&gt;
 &lt;br /&gt;
 class Node:&lt;br /&gt;
     def __init__(self, key):&lt;br /&gt;
         self.key = key&lt;br /&gt;
         self.left = self.right = None&lt;br /&gt;
 &lt;br /&gt;
 root = ...    # Wurzel des Suchbaums&lt;br /&gt;
 nodeFound = treeSearch(root, key)   # None, falls nichts gefunden&lt;br /&gt;
 &lt;br /&gt;
 def treeSearch(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return None&lt;br /&gt;
     elif node.key == key:&lt;br /&gt;
         return node&lt;br /&gt;
     elif key &amp;lt; node.key:&lt;br /&gt;
         return treeSearch(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         return treeSearch(node.right, key)&lt;br /&gt;
&lt;br /&gt;
Daraus resultiert der folgende Suchalgorithmus:&lt;br /&gt;
&lt;br /&gt;
 def treeSort(node,array):     # dynamisches Array als 2. Argument&lt;br /&gt;
     if node is None:     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
         return None:&lt;br /&gt;
     treeSort(node.left, array)     # rekursiv&lt;br /&gt;
     array.append(node.key)     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
     treeSort(node.right, array)     # rekursiv&lt;br /&gt;
&lt;br /&gt;
Komplexität:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 f(N)=\mathcal{O}(1)+f(N_\mathrm{left})+f(N_\mathrm{right})=\mathcal{O}(1)+\mathcal{O}(1)+f(N_\mathrm{leftleft})+f(N_\mathrm{leftright})+\mathcal{O}(1)+f(N_\mathrm{rightleft})&lt;br /&gt;
 +f(N_\mathrm{leftright})=N\ast\mathcal{O}(1)=\mathcal{O}(N)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sortier-Pseudocode:&lt;br /&gt;
&lt;br /&gt;
 Sortieren:&lt;br /&gt;
     (Array) a     # unsortiert&lt;br /&gt;
     (tree) t     # zunächst leer&lt;br /&gt;
 (dynamisches Array) r     # später sortiert&lt;br /&gt;
 for e in a:&lt;br /&gt;
     t = treeInsert(t, e)&lt;br /&gt;
 treeSort(t, r)&lt;br /&gt;
&lt;br /&gt;
== Insert ==&lt;br /&gt;
&lt;br /&gt;
Was passiert wenn der key (Schlüssel) schon vorhanden ist?&lt;br /&gt;
* error (exception)&lt;br /&gt;
* nichts einfügen&lt;br /&gt;
* nichts einfügen aber einen boolean return zurückgeben (einfügen=true, false)&lt;br /&gt;
* nochmals eingefügt (z.B. in der Node Klasse)&lt;br /&gt;
&lt;br /&gt;
Wobei die ersten 3 Punkte zur Mengensemantik gehören und der letzte eine Multimenge ist.&lt;br /&gt;
&lt;br /&gt;
Algorithmus im zweiten Fall (nichts einfügen):&lt;br /&gt;
&lt;br /&gt;
 def treeInsert(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return Node(key)     # Alternative Schreibweise: node = Node(key)&lt;br /&gt;
     if node.key == key:      # und dann:&lt;br /&gt;
         return node          # pass&lt;br /&gt;
     elif key &amp;lt; node.key:     # links im Baum&lt;br /&gt;
         node.left = treeInsert(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         node.right = treeInsert(node.right, key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Remove ==&lt;br /&gt;
Fälle:&lt;br /&gt;
# key (bzw. Knoten der key enthält) ist ein Blatt =&amp;gt; einfach löschen&lt;br /&gt;
# node hat &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; linken Unterbaum oder &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; rechten Unterbaum =&amp;gt; durch Unterbaum ersetzen&lt;br /&gt;
# node hat beide Unterbäume:&lt;br /&gt;
#* Suche Vorgänger: max k &amp;lt; key (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; keys); Vorgänger ist immer ein Fall 1 oder Fall 2&lt;br /&gt;
#*:=&amp;gt; ersetze node durch Vorgänger und entferne Vorgänger&lt;br /&gt;
&lt;br /&gt;
 def treePredecessor(node):     # wird nur bei Fall 3 aufgerufen&lt;br /&gt;
     node = node.left&lt;br /&gt;
     while node.right is not None:&lt;br /&gt;
         node = node.right&lt;br /&gt;
     return node&lt;br /&gt;
 &lt;br /&gt;
 def treeRemove(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return node&lt;br /&gt;
     if key &amp;lt; node.key:&lt;br /&gt;
         node.left = treeRemove(node.left, key)&lt;br /&gt;
     elif key &amp;gt; node.key:&lt;br /&gt;
         node.right = treeRemove(node.right, key)&lt;br /&gt;
     else:&lt;br /&gt;
         if node.left is None and node.right is None:     # Fall 1&lt;br /&gt;
             node = None&lt;br /&gt;
         elif node.left is None:     # Fall 2&lt;br /&gt;
             node = node.right       # +&lt;br /&gt;
         elif node.right is None:    # Fall 2&lt;br /&gt;
             node = node.left&lt;br /&gt;
         else:&lt;br /&gt;
             pred = treePredecessor(node)&lt;br /&gt;
             node.key = pred.key&lt;br /&gt;
             node.left = treeRemove(node.left, pred.key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Komplexitätsanalyse ==&lt;br /&gt;
&lt;br /&gt;
* Pfad (Zwischen node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;): &lt;br /&gt;
**Folge von Knoten (node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt;,...,node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt;), sodass:&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;ki&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;ki+1&amp;lt;/sub&amp;gt; haben eine gemeinsame Kante.&lt;br /&gt;
[[Image:Baum_Pfad.png]]&lt;br /&gt;
(Ein Baum ist ein Graph, indem es zwischen beliebigen Knoten stets genau einen Pfad gibt.)&lt;br /&gt;
&lt;br /&gt;
* Länge eines Pfades: Anzahl der Kanten = Anzahl der Knoten - 1&lt;br /&gt;
* Tiefe eines Knotens: Pfadlänge vom Knoten zur Wurzel des Baumes.&lt;br /&gt;
* Tiefe des Baumes: maximale Tiefe eines Knotens&lt;br /&gt;
* Balance eines Baumes: maximale Tiefe(k) - minimale Tiefe(k) (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; Blätter)&lt;br /&gt;
&lt;br /&gt;
== Ungünstigster Fall von treeSearch ==&lt;br /&gt;
&lt;br /&gt;
Komplexität von treeSearch = Länge des Pfades zum Knoten wo &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; gefunden wird, oder erkannt wird, dass &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; nicht im Baum ist.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; wird nicht gefunden, aber für diese Entscheidung muss der Längste Pfad vollständig durchlaufen werden.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(T)&amp;lt;/math&amp;gt; wo T = Tiefe des Baumes&lt;br /&gt;
*: [1,2,3,4,5]:&amp;lt;br /&amp;gt; [[Image:Balance.png]]&lt;br /&gt;
*: Tiefe T = 4, Balance = 4&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; wo N = Anzahl der Elemente.&lt;br /&gt;
&lt;br /&gt;
== Aufgabe ==&lt;br /&gt;
&lt;br /&gt;
Minimiere Balance (erzeuge balancierten Baum):&lt;br /&gt;
# Einfügen in geschickter Reihenfolge (siehe Übungsaufgabe)&lt;br /&gt;
# Selbstbalancierter Baum:&lt;br /&gt;
#* Überprüfen der Balance nach jedem Einfügen&lt;br /&gt;
#* Umstrukturieren des Baumes, falls Balance &amp;gt; 1 (Suchbaum-Bedingung muss erhalten bleiben)&lt;br /&gt;
#* AVL-Bäume (älteste Variante)&lt;br /&gt;
#* Rot-Schwarz-Bäume (verbreiteste Variante)&lt;br /&gt;
#* Treaps (flexibelste Variante, siehe Übung)&lt;br /&gt;
#* Splay trees&lt;br /&gt;
#* Andersson Trees (einfachste Variante)&lt;br /&gt;
(#* Skip Lists (schnellste Variante, aber kein Binärbaum))&lt;br /&gt;
&lt;br /&gt;
== Umstrukturieren, so dass Suchbaumbedingung erhalten bleibt: ==&lt;br /&gt;
&lt;br /&gt;
Rotation: elementare Umstrukturierungen&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum_Rotation.png]]&lt;br /&gt;
&lt;br /&gt;
 def rotateRight(node):&lt;br /&gt;
     newRoot = node.left&lt;br /&gt;
     node.left = newRoot.right&lt;br /&gt;
     newRoot.right = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
 def rotateLeft(node):&lt;br /&gt;
     newRoot = node.right&lt;br /&gt;
     node.right = newRoot.left&lt;br /&gt;
     newRoot.left = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
== Balance eines Suchbaumes ==&lt;br /&gt;
&lt;br /&gt;
===Balance eines Baumes zu  definieren:===&lt;br /&gt;
[[Image:Abbildung1.jpg|250px|right]]&lt;br /&gt;
*betrachte &amp;lt;tt&amp;gt;None&amp;lt;/tt&amp;gt; als zusätzlichen Knoten : '''sentinel'''   ([[englische Sprache|engl.]] für ''Wächter'')&lt;br /&gt;
*oder defeniere speziellen sentinel-Knoten&lt;br /&gt;
*RS - Pfades : von &amp;lt;tt&amp;gt;root&amp;lt;/tt&amp;gt; &amp;amp;rarr; ''sentinel''&lt;br /&gt;
*Länge eines Pfades ''P''      '''''|P|'''''&lt;br /&gt;
* &amp;lt;u&amp;gt;die Balance eines Baumes zu defenieren:&amp;lt;/u&amp;gt;&lt;br /&gt;
: '''''B = max P '''''&amp;lt;math&amp;gt; \in &amp;lt;/math&amp;gt; '''''{RS}    |P| - min |P|'''''&lt;br /&gt;
:'''''{RS}'''''  Menge aller RS-Pfade&lt;br /&gt;
;vollständiger Baum:  Balance &amp;lt;math&amp;gt;~=0&amp;lt;/math&amp;gt;&lt;br /&gt;
:Alle Knoten(außer Blättern) haben 2 Kinder.&lt;br /&gt;
;perfect balancierter Baum:  Balance  &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 1&lt;br /&gt;
'''alternative Defenition für perfect balancierte Bäume'''&lt;br /&gt;
:Für jeden Teilbaum gilt es: rechtes und linkes Kind (für jeden Knoten) sind auch wieder perfect balancierte Bäume und ihre Höhe unterscheidet sich höchstens um '''1'''.&lt;br /&gt;
&lt;br /&gt;
===Größe===&lt;br /&gt;
[[Image:baum-v.png|700px|right]]&lt;br /&gt;
====Größe des vollständigen Baumes====&lt;br /&gt;
Ebene K hat 2 &amp;lt;sup&amp;gt;k&amp;lt;/sup&amp;gt; Knoten.&lt;br /&gt;
Falls Tiefe = d , dann N = 2&amp;lt;sup&amp;gt;0&amp;lt;/sup&amp;gt; + 2&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;.....+ 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; = 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1&lt;br /&gt;
&lt;br /&gt;
====Größe des perfect balancierten Baumes====&lt;br /&gt;
Die Tiefe d, kann nicht besser sein als vollständiger Baum&lt;br /&gt;
:'''=&amp;gt;'''N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2 &amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
;schlechteste perfect balancierter Baum : ist ein vollständiger der Tiefe  d - 1 + 1, N &amp;lt;math&amp;gt; \ge&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:'''=&amp;gt;'''  2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1)   &amp;lt;math&amp;gt;~&amp;lt;&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt;)&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
d &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt;  log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; (d + 1)&lt;br /&gt;
&lt;br /&gt;
*Ergibt die Komplexität der Suche im schlechtesten Fall: Anzahl der Vergleiche pro Knoten( = 2 bzw. = 3)&amp;lt;math&amp;gt;\ast&amp;lt;/math&amp;gt;Anzahl der Knoten&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow ~f(N)\le 2d \le 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
*'''perfekt balancierter Baum '''=&amp;gt; AVL-Baum &lt;br /&gt;
*'''balancierter Baum''':&lt;br /&gt;
::&amp;lt;math&amp;gt;\forall  N:d(N)\le c \ast d (N)&amp;lt;/math&amp;gt; und  &amp;lt;math&amp;gt;1 \le c &amp;lt; \mathcal {1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:: d ist die Tiefe von perfekt balancierten Baum&lt;br /&gt;
&lt;br /&gt;
:''Komplexität der Suche'':&amp;lt;math&amp;gt; ~f(N)\le  c\ast 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
:algorithmisch einfacher als perfekt balancierter Baum, aber fast genauso schnell&lt;br /&gt;
&lt;br /&gt;
====Selbst-balancierter Baum====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  def insertTree(node,key):&lt;br /&gt;
    if node is sentinel:       #(None = sentinel)&lt;br /&gt;
        return Node(key)       &lt;br /&gt;
    if node.key == key:&lt;br /&gt;
        return Node&lt;br /&gt;
    if key &amp;lt; node.key:&lt;br /&gt;
        Node = insertTree(node.left, key)&lt;br /&gt;
    else:&lt;br /&gt;
        node.right = insertTree(node.right, key)&lt;br /&gt;
   #optimiere Balance:&lt;br /&gt;
        return node&lt;br /&gt;
&lt;br /&gt;
===Anderson-Bäume===&lt;br /&gt;
&lt;br /&gt;
  class Node:&lt;br /&gt;
    def__init__(self, key):&lt;br /&gt;
        self.key = key&lt;br /&gt;
        self.left = selft.right = None&lt;br /&gt;
        #einfügen:&lt;br /&gt;
        self.level = 1&lt;br /&gt;
&lt;br /&gt;
:''level'' : kodiert Abstand von sentinel&lt;br /&gt;
====Regeln====&lt;br /&gt;
Es gibt vertikale Kanten(parent.level == child.level + 1 ) und horizontale Kanten(parent.level == child.level)&lt;br /&gt;
:*verfeinerter Regel :  ''level kodiert reduzierten Abstand von Setinel''  (d.b. horizontale Kanten werden nicht gezählt)&lt;br /&gt;
::(die nächste zwei Regeln sichern die Balance):&lt;br /&gt;
::*alle RS-Pfade haben die gleiche reduzierte Länge oder ''root'' hat bei allen Pfaden das gleiche Level&lt;br /&gt;
::*kein Pfad hat 2 aufeinanderer folgende horizontale Kanten&lt;br /&gt;
:*nur Kanten zum rechten Kind dürfen horizontal sein&lt;br /&gt;
:*die reduzierte Höhe jedes Blatts ist '''hr=1'''&lt;br /&gt;
&lt;br /&gt;
====Beweis====&lt;br /&gt;
Vereinfachung des Algorithmus&lt;br /&gt;
:'''Satz''':ein Anderson-Baum ist balanciert.&lt;br /&gt;
*1. Sei '''hr'''- die reduzierte Höhe&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; jeder Teilbaum enthält mindestens &amp;lt;math&amp;gt;N\ge 2^{hr} -1 &amp;lt;/math&amp;gt;  Knoten&lt;br /&gt;
&lt;br /&gt;
::a). Blätter : reduzierte Höhe 1 =&amp;gt; &amp;lt;math&amp;gt; N\ge 2^{1} - 1 = 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
::b). inneren Knoten: jeder Unterbaum hat mindestens reduzierte Höhe &amp;lt;math&amp;gt; ~hr - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt;  jeder Unterbaum hat mindestens &amp;lt;math&amp;gt; ~2^{hr} -1 &amp;lt;/math&amp;gt; Knoten&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt;N \ge 2 (2^{hr-1} -1)+1 = 2^w - 2 + 1 = 2^{hr} -1&amp;lt;/math&amp;gt;&lt;br /&gt;
::::alle RS-Pfade heben gleiche Länge&lt;br /&gt;
[[Image:Ander.jpg|500px|right]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
*2. Kein Pfad hat 2 aufeinanderfolgende horizontal Kanten&lt;br /&gt;
:::&amp;lt;math&amp;gt;\Rightarrow  d \le 2 hr + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
*3. zusammen:&lt;br /&gt;
:&amp;lt;math&amp;gt;N \ge 2^{\frac {d}{2}} - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;log_{2}{ N}+1\ge log 2^{\frac {d}{2}} - 1 \ge \frac {d}{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; ~d &amp;lt; 2 log_{2}{(N + 1)}&amp;lt;/math&amp;gt;     - balancierter Baum&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
:'''Suchzeit''' : &amp;lt;math&amp;gt; ~ f(N) = \mathcal{O}(log{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Wie erreicht man die Balance===&lt;br /&gt;
====Rotation====&lt;br /&gt;
[[Image:Abbildung4.jpg|text-top|350px|Illustration von Rotation]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
   def rotateRight (node):&lt;br /&gt;
      root = node.left&lt;br /&gt;
      node.left = node.right&lt;br /&gt;
      root.right = node&lt;br /&gt;
      return root&lt;br /&gt;
   def rotateLeft(node):&lt;br /&gt;
      root = node.right&lt;br /&gt;
      node.right = node.left&lt;br /&gt;
      root.left = node&lt;br /&gt;
      return root&lt;br /&gt;
&lt;br /&gt;
====Optimierung der Balance====&lt;br /&gt;
   &lt;br /&gt;
&lt;br /&gt;
       if node.left is not sentinel and node.level==node.left.level:&lt;br /&gt;
            node &amp;gt; rotateRight(node)&lt;br /&gt;
       if node.right is not sentinel and node.right.right is  not sentinel and node.level==rotate.right.right.level:&lt;br /&gt;
            node = rotateLeft(node)&lt;br /&gt;
            node.level += 1&lt;br /&gt;
[[Image:rotate.jpg|text-top]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
[[Image:liste.png]]&lt;/div&gt;</summary>
		<author><name>Nata</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=File:Ander.jpg&amp;diff=1514</id>
		<title>File:Ander.jpg</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=File:Ander.jpg&amp;diff=1514"/>
				<updated>2008-05-29T01:15:35Z</updated>
		
		<summary type="html">&lt;p&gt;Nata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Nata</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1513</id>
		<title>Suchen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1513"/>
				<updated>2008-05-29T01:00:47Z</updated>
		
		<summary type="html">&lt;p&gt;Nata: /* Rotation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Es wäre super wenn jemand ganz kurz schreiben könnte, was am Donnerstag zu den Funktionen treeInsert/Remove/HasKey gemacht wurde, was man ja für den aktuellen Übungszettel braucht. Danke :-)&amp;lt;br /&amp;gt;&lt;br /&gt;
/edit: an eine Funktion &amp;quot;HasKey&amp;quot; kann ich mich aus der Vorlesung nicht erinnern. Wenn die jemand hat, bitte eintragen. - Protokollant&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--Hallo, ich kenne leider deinen Namen nicht, ich habe den Wiki-Eintrag von letztem Donnerstag gemacht.Da das Thema 'Dynamisches Array' noch Thema von letzter Woche war, hab ich die Wiederholung der amortisierten Kosten von diesem Mittwoch noch bei Effizienz eingetragen, du musst es also nicht noch mal in deinem Wiki eintragen. Du kannst dir aber gerne mal anschauen was ich geschrieben habe, vielleicht fallen dir noch ein paar Sachen ein, die man hätte besser machen können. lg, Franziska.--&amp;gt;&lt;br /&gt;
Das Suchen ist eine grundlegende Operation in der Informatik. Viele Probleme in der Informatik können auf Suchaufgaben zurückgeführt werden.&lt;br /&gt;
&lt;br /&gt;
Gemeint ist mit Suchen das Wiederauffinden einer oder mehrerer Datensätze aus einer Menge von früher gespeicherten Datensätzen. Ein paar einleitende Worte zum Suchproblem findet man [http://de.wikipedia.org/wiki/Suche hier].&lt;br /&gt;
&lt;br /&gt;
== Überblick verschiedener Suchmethoden ==&lt;br /&gt;
&lt;br /&gt;
Um sich der Vielseitigkeit von Suchproblemen bewusst zu werden, ist es sinnvoll, sich einen Überblick über verschiedene Suchmethoden zu verschaffen. &lt;br /&gt;
&lt;br /&gt;
Hier sei auch auf einen bereits existierenden Wikipedia-Artikel zu [http://de.wikipedia.org/wiki/Suchverfahren Suchverfahren] verwiesen.&lt;br /&gt;
&lt;br /&gt;
Allen gemeinsam ist die grundlegende Aufgabe, ein Datenelement mit bestimmten Eigenschaften aus einer großen Menge von Datenelementen zu selektieren.&lt;br /&gt;
Dies kann, natürlich ohne jeden Anspruch auf Vollständigkeit, nach einer der jetzt diskutierten Methoden geschehen:&lt;br /&gt;
&lt;br /&gt;
* '''Schlüsselsuche''': meint das Suchen von Elementen mit bestimmtem Schlüssel; ein klassisches Beispiel wäre das Suchen in einem Wörterbuch,  die Schlüssel entsprechen hier den Wörtern, die Datensätze wären die zu den Wörtern gehörigen Eintragungen.&lt;br /&gt;
&lt;br /&gt;
* '''Bereichssuche''': Im Allgemeinen meint die Bereichssuche in n-Dimensionen die Selektion von Elementen mit Eigenschaften aus einem bestimmten n-dimensionalen Volumen. Im eindimensionalen Fall will man alle Elemente finden, deren Eigenschaft(en) in einem bestimmten Intervall liegen. Die Verallgemeinerung auf n-Dimensionen ist offensichtlich. Ein Beispiel für die Bereichssuche in einer 3D-Kugel wäre ein Handy mit Geolokalisierung, welches alle Restaurants in einem Umkreis von 500m findet. Lineare Ungleichungen werden graphisch durch [http://de.wikipedia.org/wiki/Hyperebene Hyperebenen] repräsentiert. In 2D sind diese Hyperebenen Geraden. Die Ungleichungen können dann den Lösungsraum in irgendeiner Form begrenzen.&lt;br /&gt;
&lt;br /&gt;
* '''Ähnlichkeitssuche''': Finde Elemente, die gegebenen Eigenschaften möglichst ähnlich sind. Ein prominentes Beispiel ist Google (=Ähnlichkeit zwischen Suchbegriffen und Dokumenten) oder das Suchen des nächstengelegenen Restaurants (Ähnlichkeit zwischen eigener Position und Position des Restaurants). Ein wichtigster Spezialfall ist die ''nächste-nachbar Suche''.&lt;br /&gt;
&lt;br /&gt;
* '''Graphensuche''': Hier wäre beispielsweise das Problem optimaler Wege zu nennen (Navigationssuche). Dieser Punkt wird später im Verlauf der Vorlesung noch einmal aufgegriffen werden.&lt;br /&gt;
&lt;br /&gt;
Im jetzt Folgenden wird nur noch die ''Schlüsselsuche'' betrachtet werden.&lt;br /&gt;
&lt;br /&gt;
==Sequentielle Suche==&lt;br /&gt;
&lt;br /&gt;
Die ''sequentielle'' oder ''lineare'' Suche ist die 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 = sequentialSearch(a, key) &lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq &amp;lt;/math&amp;gt; foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def sequentialSearch(a, key):&lt;br /&gt;
    for i in range(len(a)):&lt;br /&gt;
        if a[i] == key:  # bzw. allgemeiner a[i].key == key &lt;br /&gt;
            return i&lt;br /&gt;
    return -1&lt;br /&gt;
&lt;br /&gt;
Wir wollen jetzt die Komplexität dieses Algorithmus bestimmen, wobei die Problemgröße durch &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt; gegeben ist. &lt;br /&gt;
&lt;br /&gt;
Dabei nimmt man an, dass der innerste Vergleich (a[i] == key) jeweils &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt; ist (diese Annahme könnte verletzt sein, wenn der Vergleichsoperator überladen ist und dadurch eine höhere Komplexität hat). Dieser Vergleich wird in der for-Schleife jeweils N-mal durchgeführt (&amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt;), so dass man nach der Verschachtelungsregel eine gesamte Komplexität von &amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt; erhält.&lt;br /&gt;
&lt;br /&gt;
Der Name ''lineare'' Suche rührt von diesem linearen Anwachsen der Komplexität mit der Arraygröße her.&lt;br /&gt;
&lt;br /&gt;
==Binäre Suche==&lt;br /&gt;
&lt;br /&gt;
Wie wir weiter unten zeigen werden, gestattet es diese Suchmethode, die Gesamtdauer der Suche in großen Datensätzen beträchtlich zu verringern. Die Methode beruht auf dem [http://de.wikipedia.org/wiki/Divide_and_Conquer Divide and Conquer-Prinzip], wobei die Suche in jedem Schritt rekursiv auf eine Hälfte des Datensatzes eingeschränkt wird. Weitere Details zur Methode sind [http://de.wikipedia.org/wiki/Bin%C3%A4re_Suche hier] zu finden. &lt;br /&gt;
&lt;br /&gt;
Die Methode ist nur dann anwendbar beziehungsweise effektiv, wenn folgendes gilt:&lt;br /&gt;
&lt;br /&gt;
# Auf der Eigenschaft der Daten, die zur Suche verwendet wird, ist eine Ordnung im Sinne von &amp;lt; oder &amp;gt; definiert.&lt;br /&gt;
# Wir wollen uns auf Datensätze beschränken, die schon fertig aufgebaut sind, in die also keine neuen Elemente mehr eingefügt werden, wenn man mit dem Suchen beginnt. Ist dies nicht der Fall, so ist unter Umständen die Implementierung über einen [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum Binärbaum] (siehe auch weiter unten) geschickter. &lt;br /&gt;
&lt;br /&gt;
Der folgende Algorithmus zeigt eine beispielhafte Implementierung der Methode (wir setzen wieder &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt;):&lt;br /&gt;
&lt;br /&gt;
 a = [...,...]     # array&lt;br /&gt;
 a.sort()   # sortiere über Ordnung des Schlüssels&lt;br /&gt;
 foundIndex = binSearch(a, key, 0, len(a))  # (Array, Schlüssel, von wo bis wo suchen im Array)&lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq&amp;lt;/math&amp;gt;  foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def binSearch(a, key, start, end):  # start ist 1. Index, end ist letzter Index + 1&lt;br /&gt;
    size = end - start   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if size &amp;lt;= 0:   # Bereich leer?  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return -1   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    center = (start + end)/2   # Integer Division, Ergebnis wird abgerundet, wichtig für ganzzahlige Indizes &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if a[center] == key:  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return center  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    elif a[center] &amp;lt; key:  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return binSearch(a, key, center + 1, end)  # Rekursion&lt;br /&gt;
    else:&lt;br /&gt;
        return binSearch(a, key, start + 1, center)  # Rekursion&lt;br /&gt;
&lt;br /&gt;
Zur Berechnung der Komplexität dieses Algorithmus vernachlässigen wir zunächst den Aufwand, den die Sortierung weiter oben verursacht. Dieser Schritt mag oder mag nicht zulässig sein.&lt;br /&gt;
&lt;br /&gt;
Nach der Sequenzregel haben auch alle &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;.  Es bleibt die Komplexität der Rekursion zu berechnen. Die gesamte Komplexität des Algorithmus (jetzt als Funktion f bezeichnet) setzt sich zusammen aus den oben erwähnten &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen sowie der Rekursion und ist &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f(N) = \mathcal{O}(1) + f(N/2) = \mathcal{O}(1) + \mathcal{O}(1) + f(N/4) = ... = \underbrace{\mathcal{O}(1) + ... + \mathcal{O}(1) + \underbrace{f(0)}_{\mathcal{O}(1)\, \rightarrow \,\mathrm{size-Abfrage}}}_{n+1 \,\mathrm{Terme}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Falls jetzt gilt &amp;lt;math&amp;gt; N = 2^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \rightarrow f(N) = \mathcal{O}(1) \cdot \mathcal{O}(n+1) = \mathcal{O}(n) = \mathcal{O}(\lg N) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Für große Datenmengen ist die ''binäre Suche'' also weit effizienter als die ''lineare Suche''. Verdoppelt sich beispielsweise die zu durchsuchende Datenmenge, so verdoppelt sich der Aufwand für die ''sequentielle Suche'' - bei der ''binären Suche'' hingegen benötigt man lediglich eine zusätzliche Vergleichsoperation. &lt;br /&gt;
&lt;br /&gt;
Für kleine Daten (&amp;lt;math&amp;gt; N = 4,\, 5 &amp;lt;/math&amp;gt;) ist die ''sequentielle Suche'' jedoch schneller als die ''binäre Suche'', da hier die rekursiven Funktionsaufrufe teurer als das Mehr an Vergleichen sind. Ein anderer ungünstiger Fall ist gegeben, wenn nur sehr wenige Suchanfragen erfolgen (weniger als &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; viele). Dann wird der Aufwand durch das Sortieren des Arrays dominiert, ist also &amp;lt;math&amp;gt;\mathcal{O}(N \lg N) &amp;lt;/math&amp;gt;. Auch dann ist sequentielle Suche vorzuziehen.&lt;br /&gt;
&lt;br /&gt;
Eine relativ einfache Möglichkeit, die ''binäre Suche'' zu verbessern, ist die sogenannte ''Interpolationssuche''. Hierbei wird die neue Position für die Suche, also die Mitte des Arrays, durch eine Schätzung ersetzt, die angibt, wo sich der Schlüssel innerhalb des Arrays befinden könnte. Bei der Suche in einem Telefonbuch nach dem Namen Zebra würde man ja auch nicht in der Mitte anfangen. Näheres hierzu im Buch von ''Sedgewick''.&lt;br /&gt;
&lt;br /&gt;
Um sich den Algorithmus der ''binären Suche'' klar zu machen, ist es instruktiv, sich die folgende Tabelle genauer anzusehen, die die sukzessive Belegung der Variablen bei verschiedenen Anfragen beschreibt. Die Testfälle wurden nach dem Prinzip des ''domain partitioning'' gewählt. Das zugehörige Array hat die Einträge&lt;br /&gt;
&lt;br /&gt;
 a = [2, 3, 4, 5, 6]&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align:center&amp;quot; border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;5&amp;quot; cellspacing=&amp;quot;0&amp;quot; &lt;br /&gt;
! gesuchter key   !!  start      !! end  !! size !! center !! return &amp;lt;br/&amp;gt; (-1 oder index)  !! Kommentare  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 4     ||0            || 5    ||  5   || 2   ||  2         || gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 2     || 0           || 5    ||  5   || 2   ||            || linker Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||           &lt;br /&gt;
|-&lt;br /&gt;
|       ||  0          || 1    ||  1   || 0   ||  0         || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 1     ||0            || 5    ||  5   || 2   ||            || links außerhalb&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 1    ||  1   || 0   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 0    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 6     ||0            || 5    ||  5   || 2   ||            || rechter Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    || 2    || 4   || 4          || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 5     ||0            || 5    ||  5   || 2   ||            || typischer Fall&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||3            || 5    ||  2   || 4   ||            ||  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       || 3           || 4    ||  1   || 3   || 3          || gefunden&lt;br /&gt;
|- &lt;br /&gt;
| 7     ||0            || 5    ||  5   || 2   ||            || rechts außerhalb        &lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    ||  2   || 4   ||            ||&lt;br /&gt;
|-&lt;br /&gt;
|       ||5            || 5    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Suche in einem Binärbaum ==&lt;br /&gt;
&lt;br /&gt;
Eine kurze Einführung in Binärbäume findet man [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum hier].&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum.png|text-top|300x300px|Zur Illustration von Bäumen]]&lt;br /&gt;
&lt;br /&gt;
Bäume sind zweidimensional verkettete Strukturen. Sie gehören zu den fundamentalen Datenstrukturen in der Informatik. Da man in Bäumen nicht nur Daten speichern kann, sondern auch relevante Beziehungen der Daten untereinander, festgelegt über eine Ordnung auf der vergleichenden Dateneigenschaft (''Schlüssel''), eignen sich Bäume also insbesondere, um gesuchte Daten schnell wieder auffinden zu können.&lt;br /&gt;
&lt;br /&gt;
Ein ''Binärbaum'' wie oben skizziert besteht aus einer Menge von ''Knoten'', die untereinander durch ''Kanten'' verbunden sind. Jeder Knoten hat einen linken und einen rechten Unterbaum, der auch leer sein kann (in Python ließe sich dies mit ''None'' implementieren). Führt eine Kante von Knoten A zu Knoten B, so heißt A Vater von B und B Kind von A. Es gibt genau einen Knoten ohne Vater, den man ''Wurzel'' nennt. Knoten ohne Kinder heißen ''Blätter''.&lt;br /&gt;
&lt;br /&gt;
Ein ''Suchbaum'' hat zusätzlich die Eigenschaft, dass die Schlüssel jedes Knotens sortiert sind. Alle Schlüssel im linken Unterbaum sind kleiner, alle Schlüssel im rechten Unterbaum sind größer als ihr Vater. Wir wollen hierbei annehmen, dass jeder Schlüssel pro Datensatz nur einmal vorkommt, da sich sonst die &amp;gt;- oder &amp;lt;-Relation nicht mehr strikt erfüllen ließe.&lt;br /&gt;
&lt;br /&gt;
Um in einem Baum suchen zu können, wollen wir von zwei Annahmen ausgehen:&lt;br /&gt;
# Einfügen und Suchen im Baum wechseln sich ab. (Wenn das Suchen erst beginnt, nachdem alle Einfügungen erfolgt sind, wäre ein dynamisches Array mit binärer Suche wie oben wesentlich einfacher.)&lt;br /&gt;
# Der Schlüssel, der die Anordnung bestimmt, kennt eine [http://de.wikipedia.org/wiki/Ordnungsrelation Ordnung] (&amp;lt;-Relation oder &amp;gt;-Relation).&lt;br /&gt;
&lt;br /&gt;
Der folgende Python-Code zeigt beispielhaft, wie man in einem Suchbaum suchen könnte. Der Konstruktor für einen Knoten des Suchbaums ließe sich zum Beispiel so implementieren:&lt;br /&gt;
 &lt;br /&gt;
 class Node:&lt;br /&gt;
     def __init__(self, key):&lt;br /&gt;
         self.key = key&lt;br /&gt;
         self.left = self.right = None&lt;br /&gt;
 &lt;br /&gt;
 root = ...    # Wurzel des Suchbaums&lt;br /&gt;
 nodeFound = treeSearch(root, key)   # None, falls nichts gefunden&lt;br /&gt;
 &lt;br /&gt;
 def treeSearch(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return None&lt;br /&gt;
     elif node.key == key:&lt;br /&gt;
         return node&lt;br /&gt;
     elif key &amp;lt; node.key:&lt;br /&gt;
         return treeSearch(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         return treeSearch(node.right, key)&lt;br /&gt;
&lt;br /&gt;
Daraus resultiert der folgende Suchalgorithmus:&lt;br /&gt;
&lt;br /&gt;
 def treeSort(node,array):     # dynamisches Array als 2. Argument&lt;br /&gt;
     if node is None:     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
         return None:&lt;br /&gt;
     treeSort(node.left, array)     # rekursiv&lt;br /&gt;
     array.append(node.key)     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
     treeSort(node.right, array)     # rekursiv&lt;br /&gt;
&lt;br /&gt;
Komplexität:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 f(N)=\mathcal{O}(1)+f(N_\mathrm{left})+f(N_\mathrm{right})=\mathcal{O}(1)+\mathcal{O}(1)+f(N_\mathrm{leftleft})+f(N_\mathrm{leftright})+\mathcal{O}(1)+f(N_\mathrm{rightleft})&lt;br /&gt;
 +f(N_\mathrm{leftright})=N\ast\mathcal{O}(1)=\mathcal{O}(N)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sortier-Pseudocode:&lt;br /&gt;
&lt;br /&gt;
 Sortieren:&lt;br /&gt;
     (Array) a     # unsortiert&lt;br /&gt;
     (tree) t     # zunächst leer&lt;br /&gt;
 (dynamisches Array) r     # später sortiert&lt;br /&gt;
 for e in a:&lt;br /&gt;
     t = treeInsert(t, e)&lt;br /&gt;
 treeSort(t, r)&lt;br /&gt;
&lt;br /&gt;
== Insert ==&lt;br /&gt;
&lt;br /&gt;
Was passiert wenn der key (Schlüssel) schon vorhanden ist?&lt;br /&gt;
* error (exception)&lt;br /&gt;
* nichts einfügen&lt;br /&gt;
* nichts einfügen aber einen boolean return zurückgeben (einfügen=true, false)&lt;br /&gt;
* nochmals eingefügt (z.B. in der Node Klasse)&lt;br /&gt;
&lt;br /&gt;
Wobei die ersten 3 Punkte zur Mengensemantik gehören und der letzte eine Multimenge ist.&lt;br /&gt;
&lt;br /&gt;
Algorithmus im zweiten Fall (nichts einfügen):&lt;br /&gt;
&lt;br /&gt;
 def treeInsert(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return Node(key)     # Alternative Schreibweise: node = Node(key)&lt;br /&gt;
     if node.key == key:      # und dann:&lt;br /&gt;
         return node          # pass&lt;br /&gt;
     elif key &amp;lt; node.key:     # links im Baum&lt;br /&gt;
         node.left = treeInsert(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         node.right = treeInsert(node.right, key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Remove ==&lt;br /&gt;
Fälle:&lt;br /&gt;
# key (bzw. Knoten der key enthält) ist ein Blatt =&amp;gt; einfach löschen&lt;br /&gt;
# node hat &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; linken Unterbaum oder &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; rechten Unterbaum =&amp;gt; durch Unterbaum ersetzen&lt;br /&gt;
# node hat beide Unterbäume:&lt;br /&gt;
#* Suche Vorgänger: max k &amp;lt; key (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; keys); Vorgänger ist immer ein Fall 1 oder Fall 2&lt;br /&gt;
#*:=&amp;gt; ersetze node durch Vorgänger und entferne Vorgänger&lt;br /&gt;
&lt;br /&gt;
 def treePredecessor(node):     # wird nur bei Fall 3 aufgerufen&lt;br /&gt;
     node = node.left&lt;br /&gt;
     while node.right is not None:&lt;br /&gt;
         node = node.right&lt;br /&gt;
     return node&lt;br /&gt;
 &lt;br /&gt;
 def treeRemove(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return node&lt;br /&gt;
     if key &amp;lt; node.key:&lt;br /&gt;
         node.left = treeRemove(node.left, key)&lt;br /&gt;
     elif key &amp;gt; node.key:&lt;br /&gt;
         node.right = treeRemove(node.right, key)&lt;br /&gt;
     else:&lt;br /&gt;
         if node.left is None and node.right is None:     # Fall 1&lt;br /&gt;
             node = None&lt;br /&gt;
         elif node.left is None:     # Fall 2&lt;br /&gt;
             node = node.right       # +&lt;br /&gt;
         elif node.right is None:    # Fall 2&lt;br /&gt;
             node = node.left&lt;br /&gt;
         else:&lt;br /&gt;
             pred = treePredecessor(node)&lt;br /&gt;
             node.key = pred.key&lt;br /&gt;
             node.left = treeRemove(node.left, pred.key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Komplexitätsanalyse ==&lt;br /&gt;
&lt;br /&gt;
* Pfad (Zwischen node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;): &lt;br /&gt;
**Folge von Knoten (node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt;,...,node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt;), sodass:&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;ki&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;ki+1&amp;lt;/sub&amp;gt; haben eine gemeinsame Kante.&lt;br /&gt;
[[Image:Baum_Pfad.png]]&lt;br /&gt;
(Ein Baum ist ein Graph, indem es zwischen beliebigen Knoten stets genau einen Pfad gibt.)&lt;br /&gt;
&lt;br /&gt;
* Länge eines Pfades: Anzahl der Kanten = Anzahl der Knoten - 1&lt;br /&gt;
* Tiefe eines Knotens: Pfadlänge vom Knoten zur Wurzel des Baumes.&lt;br /&gt;
* Tiefe des Baumes: maximale Tiefe eines Knotens&lt;br /&gt;
* Balance eines Baumes: maximale Tiefe(k) - minimale Tiefe(k) (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; Blätter)&lt;br /&gt;
&lt;br /&gt;
== Ungünstigster Fall von treeSearch ==&lt;br /&gt;
&lt;br /&gt;
Komplexität von treeSearch = Länge des Pfades zum Knoten wo &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; gefunden wird, oder erkannt wird, dass &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; nicht im Baum ist.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; wird nicht gefunden, aber für diese Entscheidung muss der Längste Pfad vollständig durchlaufen werden.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(T)&amp;lt;/math&amp;gt; wo T = Tiefe des Baumes&lt;br /&gt;
*: [1,2,3,4,5]:&amp;lt;br /&amp;gt; [[Image:Balance.png]]&lt;br /&gt;
*: Tiefe T = 4, Balance = 4&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; wo N = Anzahl der Elemente.&lt;br /&gt;
&lt;br /&gt;
== Aufgabe ==&lt;br /&gt;
&lt;br /&gt;
Minimiere Balance (erzeuge balancierten Baum):&lt;br /&gt;
# Einfügen in geschickter Reihenfolge (siehe Übungsaufgabe)&lt;br /&gt;
# Selbstbalancierter Baum:&lt;br /&gt;
#* Überprüfen der Balance nach jedem Einfügen&lt;br /&gt;
#* Umstrukturieren des Baumes, falls Balance &amp;gt; 1 (Suchbaum-Bedingung muss erhalten bleiben)&lt;br /&gt;
#* AVL-Bäume (älteste Variante)&lt;br /&gt;
#* Rot-Schwarz-Bäume (verbreiteste Variante)&lt;br /&gt;
#* Treaps (flexibelste Variante, siehe Übung)&lt;br /&gt;
#* Splay trees&lt;br /&gt;
#* Andersson Trees (einfachste Variante)&lt;br /&gt;
(#* Skip Lists (schnellste Variante, aber kein Binärbaum))&lt;br /&gt;
&lt;br /&gt;
== Umstrukturieren, so dass Suchbaumbedingung erhalten bleibt: ==&lt;br /&gt;
&lt;br /&gt;
Rotation: elementare Umstrukturierungen&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum_Rotation.png]]&lt;br /&gt;
&lt;br /&gt;
 def rotateRight(node):&lt;br /&gt;
     newRoot = node.left&lt;br /&gt;
     node.left = newRoot.right&lt;br /&gt;
     newRoot.right = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
 def rotateLeft(node):&lt;br /&gt;
     newRoot = node.right&lt;br /&gt;
     node.right = newRoot.left&lt;br /&gt;
     newRoot.left = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
== Balance eines Suchbaumes ==&lt;br /&gt;
&lt;br /&gt;
===Balance eines Baumes zu  definieren:===&lt;br /&gt;
[[Image:Abbildung1.jpg|250px|right]]&lt;br /&gt;
*betrachte &amp;lt;tt&amp;gt;None&amp;lt;/tt&amp;gt; als zusätzlichen Knoten : '''sentinel'''   ([[englische Sprache|engl.]] für ''Wächter'')&lt;br /&gt;
*oder defeniere speziellen sentinel-Knoten&lt;br /&gt;
*RS - Pfades : von &amp;lt;tt&amp;gt;root&amp;lt;/tt&amp;gt; &amp;amp;rarr; ''sentinel''&lt;br /&gt;
*Länge eines Pfades ''P''      '''''|P|'''''&lt;br /&gt;
* &amp;lt;u&amp;gt;die Balance eines Baumes zu defenieren:&amp;lt;/u&amp;gt;&lt;br /&gt;
: '''''B = max P '''''&amp;lt;math&amp;gt; \in &amp;lt;/math&amp;gt; '''''{RS}    |P| - min |P|'''''&lt;br /&gt;
:'''''{RS}'''''  Menge aller RS-Pfade&lt;br /&gt;
;vollständiger Baum:  Balance &amp;lt;math&amp;gt;~=0&amp;lt;/math&amp;gt;&lt;br /&gt;
:Alle Knoten(außer Blättern) haben 2 Kinder.&lt;br /&gt;
;perfect balancierter Baum:  Balance  &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 1&lt;br /&gt;
'''alternative Defenition für perfect balancierte Bäume'''&lt;br /&gt;
:Für jeden Teilbaum gilt es: rechtes und linkes Kind (für jeden Knoten) sind auch wieder perfect balancierte Bäume und ihre Höhe unterscheidet sich höchstens um '''1'''.&lt;br /&gt;
&lt;br /&gt;
===Größe===&lt;br /&gt;
[[Image:baum-v.png|700px|right]]&lt;br /&gt;
====Größe des vollständigen Baumes====&lt;br /&gt;
Ebene K hat 2 &amp;lt;sup&amp;gt;k&amp;lt;/sup&amp;gt; Knoten.&lt;br /&gt;
Falls Tiefe = d , dann N = 2&amp;lt;sup&amp;gt;0&amp;lt;/sup&amp;gt; + 2&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;.....+ 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; = 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1&lt;br /&gt;
&lt;br /&gt;
====Größe des perfect balancierten Baumes====&lt;br /&gt;
Die Tiefe d, kann nicht besser sein als vollständiger Baum&lt;br /&gt;
:'''=&amp;gt;'''N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2 &amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
;schlechteste perfect balancierter Baum : ist ein vollständiger der Tiefe  d - 1 + 1, N &amp;lt;math&amp;gt; \ge&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:'''=&amp;gt;'''  2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1)   &amp;lt;math&amp;gt;~&amp;lt;&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt;)&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
d &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt;  log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; (d + 1)&lt;br /&gt;
&lt;br /&gt;
*Ergibt die Komplexität der Suche im schlechtesten Fall: Anzahl der Vergleiche pro Knoten( = 2 bzw. = 3)&amp;lt;math&amp;gt;\ast&amp;lt;/math&amp;gt;Anzahl der Knoten&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow ~f(N)\le 2d \le 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
*'''perfekt balancierter Baum '''=&amp;gt; AVL-Baum &lt;br /&gt;
*'''balancierter Baum''':&lt;br /&gt;
::&amp;lt;math&amp;gt;\forall  N:d(N)\le c \ast d (N)&amp;lt;/math&amp;gt; und  &amp;lt;math&amp;gt;1 \le c &amp;lt; \mathcal {1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:: d ist die Tiefe von perfekt balancierten Baum&lt;br /&gt;
&lt;br /&gt;
:''Komplexität der Suche'':&amp;lt;math&amp;gt; ~f(N)\le  c\ast 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
:algorithmisch einfacher als perfekt balancierter Baum, aber fast genauso schnell&lt;br /&gt;
&lt;br /&gt;
====Selbst-balancierter Baum====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  def insertTree(node,key):&lt;br /&gt;
    if node is sentinel:       #(None = sentinel)&lt;br /&gt;
        return Node(key)       &lt;br /&gt;
    if node.key == key:&lt;br /&gt;
        return Node&lt;br /&gt;
    if key &amp;lt; node.key:&lt;br /&gt;
        Node = insertTree(node.left, key)&lt;br /&gt;
    else:&lt;br /&gt;
        node.right = insertTree(node.right, key)&lt;br /&gt;
   #optimiere Balance:&lt;br /&gt;
        return node&lt;br /&gt;
&lt;br /&gt;
===Anderson-Bäume===&lt;br /&gt;
&lt;br /&gt;
  class Node:&lt;br /&gt;
    def__init__(self, key):&lt;br /&gt;
        self.key = key&lt;br /&gt;
        self.left = selft.right = None&lt;br /&gt;
        #einfügen:&lt;br /&gt;
        self.level = 1&lt;br /&gt;
&lt;br /&gt;
:''level'' : kodiert Abstand von sentinel&lt;br /&gt;
====Regeln====&lt;br /&gt;
Es gibt vertikale Kanten(parent.level == child.level + 1 ) und horizontale Kanten(parent.level == child.level)&lt;br /&gt;
:*verfeinerter Regel :  ''level kodiert reduzierten Abstand von Setinel''  (d.b. horizontale Kanten werden nicht gezählt)&lt;br /&gt;
::(die nächste zwei Regeln sichern die Balance):&lt;br /&gt;
::*alle RS-Pfade haben die gleiche reduzierte Länge oder ''root'' hat bei allen Pfaden das gleiche Level&lt;br /&gt;
::*kein Pfad hat 2 aufeinanderer folgende horizontale Kanten&lt;br /&gt;
:*nur Kanten zum rechten Kind dürfen horizontal sein&lt;br /&gt;
:*die reduzierte Höhe jedes Blatts ist '''hr=1'''&lt;br /&gt;
&lt;br /&gt;
====Beweis====&lt;br /&gt;
Vereinfachung des Algorithmus&lt;br /&gt;
:'''Satz''':ein Anderson-Baum ist balanciert.&lt;br /&gt;
*1. Sei '''hr'''- die reduzierte Höhe&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; jeder Teilbaum enthält mindestens &amp;lt;math&amp;gt;N\ge 2^{hr} -1 &amp;lt;/math&amp;gt;  Knoten&lt;br /&gt;
&lt;br /&gt;
::a). Blätter : reduzierte Höhe 1 =&amp;gt; &amp;lt;math&amp;gt; N\ge 2^{1} - 1 = 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
::b). inneren Knoten: jeder Unterbaum hat mindestens reduzierte Höhe &amp;lt;math&amp;gt; ~hr - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt;  jeder Unterbaum hat mindestens &amp;lt;math&amp;gt; ~2^{hr} -1 &amp;lt;/math&amp;gt; Knoten&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt;N \ge 2 (2^{hr-1} -1)+1 = 2^w - 2 + 1 = 2^{hr} -1&amp;lt;/math&amp;gt;&lt;br /&gt;
::::alle RS-Pfade heben gleiche Länge&lt;br /&gt;
[[Image:Anderson.png|700px|right]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
*2. Kein Pfad hat 2 aufeinanderfolgende horizontal Kanten&lt;br /&gt;
:::&amp;lt;math&amp;gt;\Rightarrow  d \le 2 hr + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
*3. zusammen:&lt;br /&gt;
:&amp;lt;math&amp;gt;N \ge 2^{\frac {d}{2}} - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;log_{2}{ N}+1\ge log 2^{\frac {d}{2}} - 1 \ge \frac {d}{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; ~d &amp;lt; 2 log_{2}{(N + 1)}&amp;lt;/math&amp;gt;     - balancierter Baum&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
:'''Suchzeit''' : &amp;lt;math&amp;gt; ~ f(N) = \mathcal{O}(log{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Wie erreicht man die Balance===&lt;br /&gt;
====Rotation====&lt;br /&gt;
[[Image:Abbildung4.jpg|text-top|350px|Illustration von Rotation]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
   def rotateRight (node):&lt;br /&gt;
      root = node.left&lt;br /&gt;
      node.left = node.right&lt;br /&gt;
      root.right = node&lt;br /&gt;
      return root&lt;br /&gt;
   def rotateLeft(node):&lt;br /&gt;
      root = node.right&lt;br /&gt;
      node.right = node.left&lt;br /&gt;
      root.left = node&lt;br /&gt;
      return root&lt;br /&gt;
&lt;br /&gt;
====Optimierung der Balance====&lt;br /&gt;
   &lt;br /&gt;
&lt;br /&gt;
       if node.left is not sentinel and node.level==node.left.level:&lt;br /&gt;
            node &amp;gt; rotateRight(node)&lt;br /&gt;
       if node.right is not sentinel and node.right.right is  not sentinel and node.level==rotate.right.right.level:&lt;br /&gt;
            node = rotateLeft(node)&lt;br /&gt;
            node.level += 1&lt;br /&gt;
[[Image:rotate.jpg|text-top]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
[[Image:liste.png]]&lt;/div&gt;</summary>
		<author><name>Nata</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1509</id>
		<title>Suchen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1509"/>
				<updated>2008-05-29T00:36:47Z</updated>
		
		<summary type="html">&lt;p&gt;Nata: /* Rotation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Es wäre super wenn jemand ganz kurz schreiben könnte, was am Donnerstag zu den Funktionen treeInsert/Remove/HasKey gemacht wurde, was man ja für den aktuellen Übungszettel braucht. Danke :-)&amp;lt;br /&amp;gt;&lt;br /&gt;
/edit: an eine Funktion &amp;quot;HasKey&amp;quot; kann ich mich aus der Vorlesung nicht erinnern. Wenn die jemand hat, bitte eintragen. - Protokollant&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--Hallo, ich kenne leider deinen Namen nicht, ich habe den Wiki-Eintrag von letztem Donnerstag gemacht.Da das Thema 'Dynamisches Array' noch Thema von letzter Woche war, hab ich die Wiederholung der amortisierten Kosten von diesem Mittwoch noch bei Effizienz eingetragen, du musst es also nicht noch mal in deinem Wiki eintragen. Du kannst dir aber gerne mal anschauen was ich geschrieben habe, vielleicht fallen dir noch ein paar Sachen ein, die man hätte besser machen können. lg, Franziska.--&amp;gt;&lt;br /&gt;
Das Suchen ist eine grundlegende Operation in der Informatik. Viele Probleme in der Informatik können auf Suchaufgaben zurückgeführt werden.&lt;br /&gt;
&lt;br /&gt;
Gemeint ist mit Suchen das Wiederauffinden einer oder mehrerer Datensätze aus einer Menge von früher gespeicherten Datensätzen. Ein paar einleitende Worte zum Suchproblem findet man [http://de.wikipedia.org/wiki/Suche hier].&lt;br /&gt;
&lt;br /&gt;
== Überblick verschiedener Suchmethoden ==&lt;br /&gt;
&lt;br /&gt;
Um sich der Vielseitigkeit von Suchproblemen bewusst zu werden, ist es sinnvoll, sich einen Überblick über verschiedene Suchmethoden zu verschaffen. &lt;br /&gt;
&lt;br /&gt;
Hier sei auch auf einen bereits existierenden Wikipedia-Artikel zu [http://de.wikipedia.org/wiki/Suchverfahren Suchverfahren] verwiesen.&lt;br /&gt;
&lt;br /&gt;
Allen gemeinsam ist die grundlegende Aufgabe, ein Datenelement mit bestimmten Eigenschaften aus einer großen Menge von Datenelementen zu selektieren.&lt;br /&gt;
Dies kann, natürlich ohne jeden Anspruch auf Vollständigkeit, nach einer der jetzt diskutierten Methoden geschehen:&lt;br /&gt;
&lt;br /&gt;
* '''Schlüsselsuche''': meint das Suchen von Elementen mit bestimmtem Schlüssel; ein klassisches Beispiel wäre das Suchen in einem Wörterbuch,  die Schlüssel entsprechen hier den Wörtern, die Datensätze wären die zu den Wörtern gehörigen Eintragungen.&lt;br /&gt;
&lt;br /&gt;
* '''Bereichssuche''': Im Allgemeinen meint die Bereichssuche in n-Dimensionen die Selektion von Elementen mit Eigenschaften aus einem bestimmten n-dimensionalen Volumen. Im eindimensionalen Fall will man alle Elemente finden, deren Eigenschaft(en) in einem bestimmten Intervall liegen. Die Verallgemeinerung auf n-Dimensionen ist offensichtlich. Ein Beispiel für die Bereichssuche in einer 3D-Kugel wäre ein Handy mit Geolokalisierung, welches alle Restaurants in einem Umkreis von 500m findet. Lineare Ungleichungen werden graphisch durch [http://de.wikipedia.org/wiki/Hyperebene Hyperebenen] repräsentiert. In 2D sind diese Hyperebenen Geraden. Die Ungleichungen können dann den Lösungsraum in irgendeiner Form begrenzen.&lt;br /&gt;
&lt;br /&gt;
* '''Ähnlichkeitssuche''': Finde Elemente, die gegebenen Eigenschaften möglichst ähnlich sind. Ein prominentes Beispiel ist Google (=Ähnlichkeit zwischen Suchbegriffen und Dokumenten) oder das Suchen des nächstengelegenen Restaurants (Ähnlichkeit zwischen eigener Position und Position des Restaurants). Ein wichtigster Spezialfall ist die ''nächste-nachbar Suche''.&lt;br /&gt;
&lt;br /&gt;
* '''Graphensuche''': Hier wäre beispielsweise das Problem optimaler Wege zu nennen (Navigationssuche). Dieser Punkt wird später im Verlauf der Vorlesung noch einmal aufgegriffen werden.&lt;br /&gt;
&lt;br /&gt;
Im jetzt Folgenden wird nur noch die ''Schlüsselsuche'' betrachtet werden.&lt;br /&gt;
&lt;br /&gt;
==Sequentielle Suche==&lt;br /&gt;
&lt;br /&gt;
Die ''sequentielle'' oder ''lineare'' Suche ist die 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 = sequentialSearch(a, key) &lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq &amp;lt;/math&amp;gt; foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def sequentialSearch(a, key):&lt;br /&gt;
    for i in range(len(a)):&lt;br /&gt;
        if a[i] == key:  # bzw. allgemeiner a[i].key == key &lt;br /&gt;
            return i&lt;br /&gt;
    return -1&lt;br /&gt;
&lt;br /&gt;
Wir wollen jetzt die Komplexität dieses Algorithmus bestimmen, wobei die Problemgröße durch &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt; gegeben ist. &lt;br /&gt;
&lt;br /&gt;
Dabei nimmt man an, dass der innerste Vergleich (a[i] == key) jeweils &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt; ist (diese Annahme könnte verletzt sein, wenn der Vergleichsoperator überladen ist und dadurch eine höhere Komplexität hat). Dieser Vergleich wird in der for-Schleife jeweils N-mal durchgeführt (&amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt;), so dass man nach der Verschachtelungsregel eine gesamte Komplexität von &amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt; erhält.&lt;br /&gt;
&lt;br /&gt;
Der Name ''lineare'' Suche rührt von diesem linearen Anwachsen der Komplexität mit der Arraygröße her.&lt;br /&gt;
&lt;br /&gt;
==Binäre Suche==&lt;br /&gt;
&lt;br /&gt;
Wie wir weiter unten zeigen werden, gestattet es diese Suchmethode, die Gesamtdauer der Suche in großen Datensätzen beträchtlich zu verringern. Die Methode beruht auf dem [http://de.wikipedia.org/wiki/Divide_and_Conquer Divide and Conquer-Prinzip], wobei die Suche in jedem Schritt rekursiv auf eine Hälfte des Datensatzes eingeschränkt wird. Weitere Details zur Methode sind [http://de.wikipedia.org/wiki/Bin%C3%A4re_Suche hier] zu finden. &lt;br /&gt;
&lt;br /&gt;
Die Methode ist nur dann anwendbar beziehungsweise effektiv, wenn folgendes gilt:&lt;br /&gt;
&lt;br /&gt;
# Auf der Eigenschaft der Daten, die zur Suche verwendet wird, ist eine Ordnung im Sinne von &amp;lt; oder &amp;gt; definiert.&lt;br /&gt;
# Wir wollen uns auf Datensätze beschränken, die schon fertig aufgebaut sind, in die also keine neuen Elemente mehr eingefügt werden, wenn man mit dem Suchen beginnt. Ist dies nicht der Fall, so ist unter Umständen die Implementierung über einen [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum Binärbaum] (siehe auch weiter unten) geschickter. &lt;br /&gt;
&lt;br /&gt;
Der folgende Algorithmus zeigt eine beispielhafte Implementierung der Methode (wir setzen wieder &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt;):&lt;br /&gt;
&lt;br /&gt;
 a = [...,...]     # array&lt;br /&gt;
 a.sort()   # sortiere über Ordnung des Schlüssels&lt;br /&gt;
 foundIndex = binSearch(a, key, 0, len(a))  # (Array, Schlüssel, von wo bis wo suchen im Array)&lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq&amp;lt;/math&amp;gt;  foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def binSearch(a, key, start, end):  # start ist 1. Index, end ist letzter Index + 1&lt;br /&gt;
    size = end - start   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if size &amp;lt;= 0:   # Bereich leer?  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return -1   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    center = (start + end)/2   # Integer Division, Ergebnis wird abgerundet, wichtig für ganzzahlige Indizes &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if a[center] == key:  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return center  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    elif a[center] &amp;lt; key:  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return binSearch(a, key, center + 1, end)  # Rekursion&lt;br /&gt;
    else:&lt;br /&gt;
        return binSearch(a, key, start + 1, center)  # Rekursion&lt;br /&gt;
&lt;br /&gt;
Zur Berechnung der Komplexität dieses Algorithmus vernachlässigen wir zunächst den Aufwand, den die Sortierung weiter oben verursacht. Dieser Schritt mag oder mag nicht zulässig sein.&lt;br /&gt;
&lt;br /&gt;
Nach der Sequenzregel haben auch alle &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;.  Es bleibt die Komplexität der Rekursion zu berechnen. Die gesamte Komplexität des Algorithmus (jetzt als Funktion f bezeichnet) setzt sich zusammen aus den oben erwähnten &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen sowie der Rekursion und ist &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f(N) = \mathcal{O}(1) + f(N/2) = \mathcal{O}(1) + \mathcal{O}(1) + f(N/4) = ... = \underbrace{\mathcal{O}(1) + ... + \mathcal{O}(1) + \underbrace{f(0)}_{\mathcal{O}(1)\, \rightarrow \,\mathrm{size-Abfrage}}}_{n+1 \,\mathrm{Terme}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Falls jetzt gilt &amp;lt;math&amp;gt; N = 2^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \rightarrow f(N) = \mathcal{O}(1) \cdot \mathcal{O}(n+1) = \mathcal{O}(n) = \mathcal{O}(\lg N) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Für große Datenmengen ist die ''binäre Suche'' also weit effizienter als die ''lineare Suche''. Verdoppelt sich beispielsweise die zu durchsuchende Datenmenge, so verdoppelt sich der Aufwand für die ''sequentielle Suche'' - bei der ''binären Suche'' hingegen benötigt man lediglich eine zusätzliche Vergleichsoperation. &lt;br /&gt;
&lt;br /&gt;
Für kleine Daten (&amp;lt;math&amp;gt; N = 4,\, 5 &amp;lt;/math&amp;gt;) ist die ''sequentielle Suche'' jedoch schneller als die ''binäre Suche'', da hier die rekursiven Funktionsaufrufe teurer als das Mehr an Vergleichen sind. Ein anderer ungünstiger Fall ist gegeben, wenn nur sehr wenige Suchanfragen erfolgen (weniger als &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; viele). Dann wird der Aufwand durch das Sortieren des Arrays dominiert, ist also &amp;lt;math&amp;gt;\mathcal{O}(N \lg N) &amp;lt;/math&amp;gt;. Auch dann ist sequentielle Suche vorzuziehen.&lt;br /&gt;
&lt;br /&gt;
Eine relativ einfache Möglichkeit, die ''binäre Suche'' zu verbessern, ist die sogenannte ''Interpolationssuche''. Hierbei wird die neue Position für die Suche, also die Mitte des Arrays, durch eine Schätzung ersetzt, die angibt, wo sich der Schlüssel innerhalb des Arrays befinden könnte. Bei der Suche in einem Telefonbuch nach dem Namen Zebra würde man ja auch nicht in der Mitte anfangen. Näheres hierzu im Buch von ''Sedgewick''.&lt;br /&gt;
&lt;br /&gt;
Um sich den Algorithmus der ''binären Suche'' klar zu machen, ist es instruktiv, sich die folgende Tabelle genauer anzusehen, die die sukzessive Belegung der Variablen bei verschiedenen Anfragen beschreibt. Die Testfälle wurden nach dem Prinzip des ''domain partitioning'' gewählt. Das zugehörige Array hat die Einträge&lt;br /&gt;
&lt;br /&gt;
 a = [2, 3, 4, 5, 6]&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align:center&amp;quot; border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;5&amp;quot; cellspacing=&amp;quot;0&amp;quot; &lt;br /&gt;
! gesuchter key   !!  start      !! end  !! size !! center !! return &amp;lt;br/&amp;gt; (-1 oder index)  !! Kommentare  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 4     ||0            || 5    ||  5   || 2   ||  2         || gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 2     || 0           || 5    ||  5   || 2   ||            || linker Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||           &lt;br /&gt;
|-&lt;br /&gt;
|       ||  0          || 1    ||  1   || 0   ||  0         || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 1     ||0            || 5    ||  5   || 2   ||            || links außerhalb&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 1    ||  1   || 0   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 0    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 6     ||0            || 5    ||  5   || 2   ||            || rechter Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    || 2    || 4   || 4          || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 5     ||0            || 5    ||  5   || 2   ||            || typischer Fall&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||3            || 5    ||  2   || 4   ||            ||  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       || 3           || 4    ||  1   || 3   || 3          || gefunden&lt;br /&gt;
|- &lt;br /&gt;
| 7     ||0            || 5    ||  5   || 2   ||            || rechts außerhalb        &lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    ||  2   || 4   ||            ||&lt;br /&gt;
|-&lt;br /&gt;
|       ||5            || 5    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Suche in einem Binärbaum ==&lt;br /&gt;
&lt;br /&gt;
Eine kurze Einführung in Binärbäume findet man [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum hier].&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum.png|text-top|300x300px|Zur Illustration von Bäumen]]&lt;br /&gt;
&lt;br /&gt;
Bäume sind zweidimensional verkettete Strukturen. Sie gehören zu den fundamentalen Datenstrukturen in der Informatik. Da man in Bäumen nicht nur Daten speichern kann, sondern auch relevante Beziehungen der Daten untereinander, festgelegt über eine Ordnung auf der vergleichenden Dateneigenschaft (''Schlüssel''), eignen sich Bäume also insbesondere, um gesuchte Daten schnell wieder auffinden zu können.&lt;br /&gt;
&lt;br /&gt;
Ein ''Binärbaum'' wie oben skizziert besteht aus einer Menge von ''Knoten'', die untereinander durch ''Kanten'' verbunden sind. Jeder Knoten hat einen linken und einen rechten Unterbaum, der auch leer sein kann (in Python ließe sich dies mit ''None'' implementieren). Führt eine Kante von Knoten A zu Knoten B, so heißt A Vater von B und B Kind von A. Es gibt genau einen Knoten ohne Vater, den man ''Wurzel'' nennt. Knoten ohne Kinder heißen ''Blätter''.&lt;br /&gt;
&lt;br /&gt;
Ein ''Suchbaum'' hat zusätzlich die Eigenschaft, dass die Schlüssel jedes Knotens sortiert sind. Alle Schlüssel im linken Unterbaum sind kleiner, alle Schlüssel im rechten Unterbaum sind größer als ihr Vater. Wir wollen hierbei annehmen, dass jeder Schlüssel pro Datensatz nur einmal vorkommt, da sich sonst die &amp;gt;- oder &amp;lt;-Relation nicht mehr strikt erfüllen ließe.&lt;br /&gt;
&lt;br /&gt;
Um in einem Baum suchen zu können, wollen wir von zwei Annahmen ausgehen:&lt;br /&gt;
# Einfügen und Suchen im Baum wechseln sich ab. (Wenn das Suchen erst beginnt, nachdem alle Einfügungen erfolgt sind, wäre ein dynamisches Array mit binärer Suche wie oben wesentlich einfacher.)&lt;br /&gt;
# Der Schlüssel, der die Anordnung bestimmt, kennt eine [http://de.wikipedia.org/wiki/Ordnungsrelation Ordnung] (&amp;lt;-Relation oder &amp;gt;-Relation).&lt;br /&gt;
&lt;br /&gt;
Der folgende Python-Code zeigt beispielhaft, wie man in einem Suchbaum suchen könnte. Der Konstruktor für einen Knoten des Suchbaums ließe sich zum Beispiel so implementieren:&lt;br /&gt;
 &lt;br /&gt;
 class Node:&lt;br /&gt;
     def __init__(self, key):&lt;br /&gt;
         self.key = key&lt;br /&gt;
         self.left = self.right = None&lt;br /&gt;
 &lt;br /&gt;
 root = ...    # Wurzel des Suchbaums&lt;br /&gt;
 nodeFound = treeSearch(root, key)   # None, falls nichts gefunden&lt;br /&gt;
 &lt;br /&gt;
 def treeSearch(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return None&lt;br /&gt;
     elif node.key == key:&lt;br /&gt;
         return node&lt;br /&gt;
     elif key &amp;lt; node.key:&lt;br /&gt;
         return treeSearch(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         return treeSearch(node.right, key)&lt;br /&gt;
&lt;br /&gt;
Daraus resultiert der folgende Suchalgorithmus:&lt;br /&gt;
&lt;br /&gt;
 def treeSort(node,array):     # dynamisches Array als 2. Argument&lt;br /&gt;
     if node is None:     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
         return None:&lt;br /&gt;
     treeSort(node.left, array)     # rekursiv&lt;br /&gt;
     array.append(node.key)     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
     treeSort(node.right, array)     # rekursiv&lt;br /&gt;
&lt;br /&gt;
Komplexität:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 f(N)=\mathcal{O}(1)+f(N_\mathrm{left})+f(N_\mathrm{right})=\mathcal{O}(1)+\mathcal{O}(1)+f(N_\mathrm{leftleft})+f(N_\mathrm{leftright})+\mathcal{O}(1)+f(N_\mathrm{rightleft})&lt;br /&gt;
 +f(N_\mathrm{leftright})=N\ast\mathcal{O}(1)=\mathcal{O}(N)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sortier-Pseudocode:&lt;br /&gt;
&lt;br /&gt;
 Sortieren:&lt;br /&gt;
     (Array) a     # unsortiert&lt;br /&gt;
     (tree) t     # zunächst leer&lt;br /&gt;
 (dynamisches Array) r     # später sortiert&lt;br /&gt;
 for e in a:&lt;br /&gt;
     t = treeInsert(t, e)&lt;br /&gt;
 treeSort(t, r)&lt;br /&gt;
&lt;br /&gt;
== Insert ==&lt;br /&gt;
&lt;br /&gt;
Was passiert wenn der key (Schlüssel) schon vorhanden ist?&lt;br /&gt;
* error (exception)&lt;br /&gt;
* nichts einfügen&lt;br /&gt;
* nichts einfügen aber einen boolean return zurückgeben (einfügen=true, false)&lt;br /&gt;
* nochmals eingefügt (z.B. in der Node Klasse)&lt;br /&gt;
&lt;br /&gt;
Wobei die ersten 3 Punkte zur Mengensemantik gehören und der letzte eine Multimenge ist.&lt;br /&gt;
&lt;br /&gt;
Algorithmus im zweiten Fall (nichts einfügen):&lt;br /&gt;
&lt;br /&gt;
 def treeInsert(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return Node(key)     # Alternative Schreibweise: node = Node(key)&lt;br /&gt;
     if node.key == key:      # und dann:&lt;br /&gt;
         return node          # pass&lt;br /&gt;
     elif key &amp;lt; node.key:     # links im Baum&lt;br /&gt;
         node.left = treeInsert(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         node.right = treeInsert(node.right, key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Remove ==&lt;br /&gt;
Fälle:&lt;br /&gt;
# key (bzw. Knoten der key enthält) ist ein Blatt =&amp;gt; einfach löschen&lt;br /&gt;
# node hat &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; linken Unterbaum oder &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; rechten Unterbaum =&amp;gt; durch Unterbaum ersetzen&lt;br /&gt;
# node hat beide Unterbäume:&lt;br /&gt;
#* Suche Vorgänger: max k &amp;lt; key (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; keys); Vorgänger ist immer ein Fall 1 oder Fall 2&lt;br /&gt;
#*:=&amp;gt; ersetze node durch Vorgänger und entferne Vorgänger&lt;br /&gt;
&lt;br /&gt;
 def treePredecessor(node):     # wird nur bei Fall 3 aufgerufen&lt;br /&gt;
     node = node.left&lt;br /&gt;
     while node.right is not None:&lt;br /&gt;
         node = node.right&lt;br /&gt;
     return node&lt;br /&gt;
 &lt;br /&gt;
 def treeRemove(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return node&lt;br /&gt;
     if key &amp;lt; node.key:&lt;br /&gt;
         node.left = treeRemove(node.left, key)&lt;br /&gt;
     elif key &amp;gt; node.key:&lt;br /&gt;
         node.right = treeRemove(node.right, key)&lt;br /&gt;
     else:&lt;br /&gt;
         if node.left is None and node.right is None:     # Fall 1&lt;br /&gt;
             node = None&lt;br /&gt;
         elif node.left is None:     # Fall 2&lt;br /&gt;
             node = node.right       # +&lt;br /&gt;
         elif node.right is None:    # Fall 2&lt;br /&gt;
             node = node.left&lt;br /&gt;
         else:&lt;br /&gt;
             pred = treePredecessor(node)&lt;br /&gt;
             node.key = pred.key&lt;br /&gt;
             node.left = treeRemove(node.left, pred.key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Komplexitätsanalyse ==&lt;br /&gt;
&lt;br /&gt;
* Pfad (Zwischen node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;): &lt;br /&gt;
**Folge von Knoten (node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt;,...,node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt;), sodass:&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;ki&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;ki+1&amp;lt;/sub&amp;gt; haben eine gemeinsame Kante.&lt;br /&gt;
[[Image:Baum_Pfad.png]]&lt;br /&gt;
(Ein Baum ist ein Graph, indem es zwischen beliebigen Knoten stets genau einen Pfad gibt.)&lt;br /&gt;
&lt;br /&gt;
* Länge eines Pfades: Anzahl der Kanten = Anzahl der Knoten - 1&lt;br /&gt;
* Tiefe eines Knotens: Pfadlänge vom Knoten zur Wurzel des Baumes.&lt;br /&gt;
* Tiefe des Baumes: maximale Tiefe eines Knotens&lt;br /&gt;
* Balance eines Baumes: maximale Tiefe(k) - minimale Tiefe(k) (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; Blätter)&lt;br /&gt;
&lt;br /&gt;
== Ungünstigster Fall von treeSearch ==&lt;br /&gt;
&lt;br /&gt;
Komplexität von treeSearch = Länge des Pfades zum Knoten wo &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; gefunden wird, oder erkannt wird, dass &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; nicht im Baum ist.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; wird nicht gefunden, aber für diese Entscheidung muss der Längste Pfad vollständig durchlaufen werden.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(T)&amp;lt;/math&amp;gt; wo T = Tiefe des Baumes&lt;br /&gt;
*: [1,2,3,4,5]:&amp;lt;br /&amp;gt; [[Image:Balance.png]]&lt;br /&gt;
*: Tiefe T = 4, Balance = 4&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; wo N = Anzahl der Elemente.&lt;br /&gt;
&lt;br /&gt;
== Aufgabe ==&lt;br /&gt;
&lt;br /&gt;
Minimiere Balance (erzeuge balancierten Baum):&lt;br /&gt;
# Einfügen in geschickter Reihenfolge (siehe Übungsaufgabe)&lt;br /&gt;
# Selbstbalancierter Baum:&lt;br /&gt;
#* Überprüfen der Balance nach jedem Einfügen&lt;br /&gt;
#* Umstrukturieren des Baumes, falls Balance &amp;gt; 1 (Suchbaum-Bedingung muss erhalten bleiben)&lt;br /&gt;
#* AVL-Bäume (älteste Variante)&lt;br /&gt;
#* Rot-Schwarz-Bäume (verbreiteste Variante)&lt;br /&gt;
#* Treaps (flexibelste Variante, siehe Übung)&lt;br /&gt;
#* Splay trees&lt;br /&gt;
#* Andersson Trees (einfachste Variante)&lt;br /&gt;
(#* Skip Lists (schnellste Variante, aber kein Binärbaum))&lt;br /&gt;
&lt;br /&gt;
== Umstrukturieren, so dass Suchbaumbedingung erhalten bleibt: ==&lt;br /&gt;
&lt;br /&gt;
Rotation: elementare Umstrukturierungen&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum_Rotation.png]]&lt;br /&gt;
&lt;br /&gt;
 def rotateRight(node):&lt;br /&gt;
     newRoot = node.left&lt;br /&gt;
     node.left = newRoot.right&lt;br /&gt;
     newRoot.right = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
 def rotateLeft(node):&lt;br /&gt;
     newRoot = node.right&lt;br /&gt;
     node.right = newRoot.left&lt;br /&gt;
     newRoot.left = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
== Balance eines Suchbaumes ==&lt;br /&gt;
&lt;br /&gt;
===Balance eines Baumes zu  definieren:===&lt;br /&gt;
[[Image:Abbildung1.jpg|250px|right]]&lt;br /&gt;
*betrachte &amp;lt;tt&amp;gt;None&amp;lt;/tt&amp;gt; als zusätzlichen Knoten : '''sentinel'''   ([[englische Sprache|engl.]] für ''Wächter'')&lt;br /&gt;
*oder defeniere speziellen sentinel-Knoten&lt;br /&gt;
*RS - Pfades : von &amp;lt;tt&amp;gt;root&amp;lt;/tt&amp;gt; &amp;amp;rarr; ''sentinel''&lt;br /&gt;
*Länge eines Pfades ''P''      '''''|P|'''''&lt;br /&gt;
* &amp;lt;u&amp;gt;die Balance eines Baumes zu defenieren:&amp;lt;/u&amp;gt;&lt;br /&gt;
: '''''B = max P '''''&amp;lt;math&amp;gt; \in &amp;lt;/math&amp;gt; '''''{RS}    |P| - min |P|'''''&lt;br /&gt;
:'''''{RS}'''''  Menge aller RS-Pfade&lt;br /&gt;
;vollständiger Baum:  Balance &amp;lt;math&amp;gt;~=0&amp;lt;/math&amp;gt;&lt;br /&gt;
:Alle Knoten(außer Blättern) haben 2 Kinder.&lt;br /&gt;
;perfect balancierter Baum:  Balance  &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 1&lt;br /&gt;
'''alternative Defenition für perfect balancierte Bäume'''&lt;br /&gt;
:Für jeden Teilbaum gilt es: rechtes und linkes Kind (für jeden Knoten) sind auch wieder perfect balancierte Bäume und ihre Höhe unterscheidet sich höchstens um '''1'''.&lt;br /&gt;
&lt;br /&gt;
===Größe===&lt;br /&gt;
[[Image:baum-v.png|700px|right]]&lt;br /&gt;
====Größe des vollständigen Baumes====&lt;br /&gt;
Ebene K hat 2 &amp;lt;sup&amp;gt;k&amp;lt;/sup&amp;gt; Knoten.&lt;br /&gt;
Falls Tiefe = d , dann N = 2&amp;lt;sup&amp;gt;0&amp;lt;/sup&amp;gt; + 2&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;.....+ 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; = 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1&lt;br /&gt;
&lt;br /&gt;
====Größe des perfect balancierten Baumes====&lt;br /&gt;
Die Tiefe d, kann nicht besser sein als vollständiger Baum&lt;br /&gt;
:'''=&amp;gt;'''N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2 &amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
;schlechteste perfect balancierter Baum : ist ein vollständiger der Tiefe  d - 1 + 1, N &amp;lt;math&amp;gt; \ge&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:'''=&amp;gt;'''  2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1)   &amp;lt;math&amp;gt;~&amp;lt;&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt;)&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
d &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt;  log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; (d + 1)&lt;br /&gt;
&lt;br /&gt;
*Ergibt die Komplexität der Suche im schlechtesten Fall: Anzahl der Vergleiche pro Knoten( = 2 bzw. = 3)&amp;lt;math&amp;gt;\ast&amp;lt;/math&amp;gt;Anzahl der Knoten&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow ~f(N)\le 2d \le 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
*'''perfekt balancierter Baum '''=&amp;gt; AVL-Baum &lt;br /&gt;
*'''balancierter Baum''':&lt;br /&gt;
::&amp;lt;math&amp;gt;\forall  N:d(N)\le c \ast d (N)&amp;lt;/math&amp;gt; und  &amp;lt;math&amp;gt;1 \le c &amp;lt; \mathcal {1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:: d ist die Tiefe von perfekt balancierten Baum&lt;br /&gt;
&lt;br /&gt;
:''Komplexität der Suche'':&amp;lt;math&amp;gt; ~f(N)\le  c\ast 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
:algorithmisch einfacher als perfekt balancierter Baum, aber fast genauso schnell&lt;br /&gt;
&lt;br /&gt;
====Selbst-balancierter Baum====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  def insertTree(node,key):&lt;br /&gt;
    if node is sentinel:       #(None = sentinel)&lt;br /&gt;
        return Node(key)       &lt;br /&gt;
    if node.key == key:&lt;br /&gt;
        return Node&lt;br /&gt;
    if key &amp;lt; node.key:&lt;br /&gt;
        Node = insertTree(node.left, key)&lt;br /&gt;
    else:&lt;br /&gt;
        node.right = insertTree(node.right, key)&lt;br /&gt;
   #optimiere Balance:&lt;br /&gt;
        return node&lt;br /&gt;
&lt;br /&gt;
===Anderson-Bäume===&lt;br /&gt;
&lt;br /&gt;
  class Node:&lt;br /&gt;
    def__init__(self, key):&lt;br /&gt;
        self.key = key&lt;br /&gt;
        self.left = selft.right = None&lt;br /&gt;
        #einfügen:&lt;br /&gt;
        self.level = 1&lt;br /&gt;
&lt;br /&gt;
:''level'' : kodiert Abstand von sentinel&lt;br /&gt;
====Regeln====&lt;br /&gt;
Es gibt vertikale Kanten(parent.level == child.level + 1 ) und horizontale Kanten(parent.level == child.level)&lt;br /&gt;
:*verfeinerter Regel :  ''level kodiert reduzierten Abstand von Setinel''  (d.b. horizontale Kanten werden nicht gezählt)&lt;br /&gt;
::(die nächste zwei Regeln sichern die Balance):&lt;br /&gt;
::*alle RS-Pfade haben die gleiche reduzierte Länge oder ''root'' hat bei allen Pfaden das gleiche Level&lt;br /&gt;
::*kein Pfad hat 2 aufeinanderer folgende horizontale Kanten&lt;br /&gt;
:*nur Kanten zum rechten Kind dürfen horizontal sein&lt;br /&gt;
:*die reduzierte Höhe jedes Blatts ist '''hr=1'''&lt;br /&gt;
&lt;br /&gt;
====Beweis====&lt;br /&gt;
Vereinfachung des Algorithmus&lt;br /&gt;
:'''Satz''':ein Anderson-Baum ist balanciert.&lt;br /&gt;
*1. Sei '''hr'''- die reduzierte Höhe&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; jeder Teilbaum enthält mindestens &amp;lt;math&amp;gt;N\ge 2^{hr} -1 &amp;lt;/math&amp;gt;  Knoten&lt;br /&gt;
&lt;br /&gt;
::a). Blätter : reduzierte Höhe 1 =&amp;gt; &amp;lt;math&amp;gt; N\ge 2^{1} - 1 = 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
::b). inneren Knoten: jeder Unterbaum hat mindestens reduzierte Höhe &amp;lt;math&amp;gt; ~hr - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt;  jeder Unterbaum hat mindestens &amp;lt;math&amp;gt; ~2^{hr} -1 &amp;lt;/math&amp;gt; Knoten&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt;N \ge 2 (2^{hr-1} -1)+1 = 2^w - 2 + 1 = 2^{hr} -1&amp;lt;/math&amp;gt;&lt;br /&gt;
::::alle RS-Pfade heben gleiche Länge&lt;br /&gt;
[[Image:Anderson.png|700px|right]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
*2. Kein Pfad hat 2 aufeinanderfolgende horizontal Kanten&lt;br /&gt;
:::&amp;lt;math&amp;gt;\Rightarrow  d \le 2 hr + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
*3. zusammen:&lt;br /&gt;
:&amp;lt;math&amp;gt;N \ge 2^{\frac {d}{2}} - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;log_{2}{ N}+1\ge log 2^{\frac {d}{2}} - 1 \ge \frac {d}{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; ~d &amp;lt; 2 log_{2}{(N + 1)}&amp;lt;/math&amp;gt;     - balancierter Baum&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
:'''Suchzeit''' : &amp;lt;math&amp;gt; ~ f(N) = \mathcal{O}(log{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Wie erreicht man die Balance===&lt;br /&gt;
====Rotation====&lt;br /&gt;
[[Image:Abbildung4.jpg|text-top|350px|Illustration von Rotation]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
   def rotateRight (node):&lt;br /&gt;
      root = node.left&lt;br /&gt;
      node.left = node.right&lt;br /&gt;
      root.right = node&lt;br /&gt;
      return root&lt;br /&gt;
   def rotateLeft(node):&lt;br /&gt;
      root = node.right&lt;br /&gt;
      node.right = node.left&lt;br /&gt;
      root.left = node&lt;br /&gt;
      return root&lt;br /&gt;
&lt;br /&gt;
====Optimierung der Balance====&lt;br /&gt;
   &lt;br /&gt;
&lt;br /&gt;
       if node.left is not sentinel and node.level==node.left.level:&lt;br /&gt;
            node &amp;gt; rotateRight(node)&lt;br /&gt;
       if node.right is not sentinel and node.right.right is  not sentinel and node.level==rotate.right.right.level:&lt;br /&gt;
            node = rotateLeft(node)&lt;br /&gt;
            node.level += 1&lt;br /&gt;
[[Image:rotate.jpg|text-top]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
[[Image:liste.png]]&lt;/div&gt;</summary>
		<author><name>Nata</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1508</id>
		<title>Suchen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1508"/>
				<updated>2008-05-29T00:36:17Z</updated>
		
		<summary type="html">&lt;p&gt;Nata: /* Optimierung der Balance */&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 = sequentialSearch(a, key) &lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq &amp;lt;/math&amp;gt; foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def sequentialSearch(a, key):&lt;br /&gt;
    for i in range(len(a)):&lt;br /&gt;
        if a[i] == key:  # bzw. allgemeiner a[i].key == key &lt;br /&gt;
            return i&lt;br /&gt;
    return -1&lt;br /&gt;
&lt;br /&gt;
Wir wollen jetzt die Komplexität dieses Algorithmus bestimmen, wobei die Problemgröße durch &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt; gegeben ist. &lt;br /&gt;
&lt;br /&gt;
Dabei nimmt man an, dass der innerste Vergleich (a[i] == key) jeweils &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt; ist (diese Annahme könnte verletzt sein, wenn der Vergleichsoperator überladen ist und dadurch eine höhere Komplexität hat). Dieser Vergleich wird in der for-Schleife jeweils N-mal durchgeführt (&amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt;), so dass man nach der Verschachtelungsregel eine gesamte Komplexität von &amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt; erhält.&lt;br /&gt;
&lt;br /&gt;
Der Name ''lineare'' Suche rührt von diesem linearen Anwachsen der Komplexität mit der Arraygröße her.&lt;br /&gt;
&lt;br /&gt;
==Binäre Suche==&lt;br /&gt;
&lt;br /&gt;
Wie wir weiter unten zeigen werden, gestattet es diese Suchmethode, die Gesamtdauer der Suche in großen Datensätzen beträchtlich zu verringern. Die Methode beruht auf dem [http://de.wikipedia.org/wiki/Divide_and_Conquer Divide and Conquer-Prinzip], wobei die Suche in jedem Schritt rekursiv auf eine Hälfte des Datensatzes eingeschränkt wird. Weitere Details zur Methode sind [http://de.wikipedia.org/wiki/Bin%C3%A4re_Suche hier] zu finden. &lt;br /&gt;
&lt;br /&gt;
Die Methode ist nur dann anwendbar beziehungsweise effektiv, wenn folgendes gilt:&lt;br /&gt;
&lt;br /&gt;
# Auf der Eigenschaft der Daten, die zur Suche verwendet wird, ist eine Ordnung im Sinne von &amp;lt; oder &amp;gt; definiert.&lt;br /&gt;
# Wir wollen uns auf Datensätze beschränken, die schon fertig aufgebaut sind, in die also keine neuen Elemente mehr eingefügt werden, wenn man mit dem Suchen beginnt. Ist dies nicht der Fall, so ist unter Umständen die Implementierung über einen [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum Binärbaum] (siehe auch weiter unten) geschickter. &lt;br /&gt;
&lt;br /&gt;
Der folgende Algorithmus zeigt eine beispielhafte Implementierung der Methode (wir setzen wieder &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt;):&lt;br /&gt;
&lt;br /&gt;
 a = [...,...]     # array&lt;br /&gt;
 a.sort()   # sortiere über Ordnung des Schlüssels&lt;br /&gt;
 foundIndex = binSearch(a, key, 0, len(a))  # (Array, Schlüssel, von wo bis wo suchen im Array)&lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq&amp;lt;/math&amp;gt;  foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def binSearch(a, key, start, end):  # start ist 1. Index, end ist letzter Index + 1&lt;br /&gt;
    size = end - start   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if size &amp;lt;= 0:   # Bereich leer?  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return -1   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    center = (start + end)/2   # Integer Division, Ergebnis wird abgerundet, wichtig für ganzzahlige Indizes &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if a[center] == key:  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return center  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    elif a[center] &amp;lt; key:  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return binSearch(a, key, center + 1, end)  # Rekursion&lt;br /&gt;
    else:&lt;br /&gt;
        return binSearch(a, key, start + 1, center)  # Rekursion&lt;br /&gt;
&lt;br /&gt;
Zur Berechnung der Komplexität dieses Algorithmus vernachlässigen wir zunächst den Aufwand, den die Sortierung weiter oben verursacht. Dieser Schritt mag oder mag nicht zulässig sein.&lt;br /&gt;
&lt;br /&gt;
Nach der Sequenzregel haben auch alle &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;.  Es bleibt die Komplexität der Rekursion zu berechnen. Die gesamte Komplexität des Algorithmus (jetzt als Funktion f bezeichnet) setzt sich zusammen aus den oben erwähnten &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen sowie der Rekursion und ist &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f(N) = \mathcal{O}(1) + f(N/2) = \mathcal{O}(1) + \mathcal{O}(1) + f(N/4) = ... = \underbrace{\mathcal{O}(1) + ... + \mathcal{O}(1) + \underbrace{f(0)}_{\mathcal{O}(1)\, \rightarrow \,\mathrm{size-Abfrage}}}_{n+1 \,\mathrm{Terme}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Falls jetzt gilt &amp;lt;math&amp;gt; N = 2^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \rightarrow f(N) = \mathcal{O}(1) \cdot \mathcal{O}(n+1) = \mathcal{O}(n) = \mathcal{O}(\lg N) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Für große Datenmengen ist die ''binäre Suche'' also weit effizienter als die ''lineare Suche''. Verdoppelt sich beispielsweise die zu durchsuchende Datenmenge, so verdoppelt sich der Aufwand für die ''sequentielle Suche'' - bei der ''binären Suche'' hingegen benötigt man lediglich eine zusätzliche Vergleichsoperation. &lt;br /&gt;
&lt;br /&gt;
Für kleine Daten (&amp;lt;math&amp;gt; N = 4,\, 5 &amp;lt;/math&amp;gt;) ist die ''sequentielle Suche'' jedoch schneller als die ''binäre Suche'', da hier die rekursiven Funktionsaufrufe teurer als das Mehr an Vergleichen sind. Ein anderer ungünstiger Fall ist gegeben, wenn nur sehr wenige Suchanfragen erfolgen (weniger als &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; viele). Dann wird der Aufwand durch das Sortieren des Arrays dominiert, ist also &amp;lt;math&amp;gt;\mathcal{O}(N \lg N) &amp;lt;/math&amp;gt;. Auch dann ist sequentielle Suche vorzuziehen.&lt;br /&gt;
&lt;br /&gt;
Eine relativ einfache Möglichkeit, die ''binäre Suche'' zu verbessern, ist die sogenannte ''Interpolationssuche''. Hierbei wird die neue Position für die Suche, also die Mitte des Arrays, durch eine Schätzung ersetzt, die angibt, wo sich der Schlüssel innerhalb des Arrays befinden könnte. Bei der Suche in einem Telefonbuch nach dem Namen Zebra würde man ja auch nicht in der Mitte anfangen. Näheres hierzu im Buch von ''Sedgewick''.&lt;br /&gt;
&lt;br /&gt;
Um sich den Algorithmus der ''binären Suche'' klar zu machen, ist es instruktiv, sich die folgende Tabelle genauer anzusehen, die die sukzessive Belegung der Variablen bei verschiedenen Anfragen beschreibt. Die Testfälle wurden nach dem Prinzip des ''domain partitioning'' gewählt. Das zugehörige Array hat die Einträge&lt;br /&gt;
&lt;br /&gt;
 a = [2, 3, 4, 5, 6]&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align:center&amp;quot; border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;5&amp;quot; cellspacing=&amp;quot;0&amp;quot; &lt;br /&gt;
! gesuchter key   !!  start      !! end  !! size !! center !! return &amp;lt;br/&amp;gt; (-1 oder index)  !! Kommentare  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 4     ||0            || 5    ||  5   || 2   ||  2         || gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 2     || 0           || 5    ||  5   || 2   ||            || linker Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||           &lt;br /&gt;
|-&lt;br /&gt;
|       ||  0          || 1    ||  1   || 0   ||  0         || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 1     ||0            || 5    ||  5   || 2   ||            || links außerhalb&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 1    ||  1   || 0   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 0    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 6     ||0            || 5    ||  5   || 2   ||            || rechter Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    || 2    || 4   || 4          || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 5     ||0            || 5    ||  5   || 2   ||            || typischer Fall&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||3            || 5    ||  2   || 4   ||            ||  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       || 3           || 4    ||  1   || 3   || 3          || gefunden&lt;br /&gt;
|- &lt;br /&gt;
| 7     ||0            || 5    ||  5   || 2   ||            || rechts außerhalb        &lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    ||  2   || 4   ||            ||&lt;br /&gt;
|-&lt;br /&gt;
|       ||5            || 5    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Suche in einem Binärbaum ==&lt;br /&gt;
&lt;br /&gt;
Eine kurze Einführung in Binärbäume findet man [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum hier].&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum.png|text-top|300x300px|Zur Illustration von Bäumen]]&lt;br /&gt;
&lt;br /&gt;
Bäume sind zweidimensional verkettete Strukturen. Sie gehören zu den fundamentalen Datenstrukturen in der Informatik. Da man in Bäumen nicht nur Daten speichern kann, sondern auch relevante Beziehungen der Daten untereinander, festgelegt über eine Ordnung auf der vergleichenden Dateneigenschaft (''Schlüssel''), eignen sich Bäume also insbesondere, um gesuchte Daten schnell wieder auffinden zu können.&lt;br /&gt;
&lt;br /&gt;
Ein ''Binärbaum'' wie oben skizziert besteht aus einer Menge von ''Knoten'', die untereinander durch ''Kanten'' verbunden sind. Jeder Knoten hat einen linken und einen rechten Unterbaum, der auch leer sein kann (in Python ließe sich dies mit ''None'' implementieren). Führt eine Kante von Knoten A zu Knoten B, so heißt A Vater von B und B Kind von A. Es gibt genau einen Knoten ohne Vater, den man ''Wurzel'' nennt. Knoten ohne Kinder heißen ''Blätter''.&lt;br /&gt;
&lt;br /&gt;
Ein ''Suchbaum'' hat zusätzlich die Eigenschaft, dass die Schlüssel jedes Knotens sortiert sind. Alle Schlüssel im linken Unterbaum sind kleiner, alle Schlüssel im rechten Unterbaum sind größer als ihr Vater. Wir wollen hierbei annehmen, dass jeder Schlüssel pro Datensatz nur einmal vorkommt, da sich sonst die &amp;gt;- oder &amp;lt;-Relation nicht mehr strikt erfüllen ließe.&lt;br /&gt;
&lt;br /&gt;
Um in einem Baum suchen zu können, wollen wir von zwei Annahmen ausgehen:&lt;br /&gt;
# Einfügen und Suchen im Baum wechseln sich ab. (Wenn das Suchen erst beginnt, nachdem alle Einfügungen erfolgt sind, wäre ein dynamisches Array mit binärer Suche wie oben wesentlich einfacher.)&lt;br /&gt;
# Der Schlüssel, der die Anordnung bestimmt, kennt eine [http://de.wikipedia.org/wiki/Ordnungsrelation Ordnung] (&amp;lt;-Relation oder &amp;gt;-Relation).&lt;br /&gt;
&lt;br /&gt;
Der folgende Python-Code zeigt beispielhaft, wie man in einem Suchbaum suchen könnte. Der Konstruktor für einen Knoten des Suchbaums ließe sich zum Beispiel so implementieren:&lt;br /&gt;
 &lt;br /&gt;
 class Node:&lt;br /&gt;
     def __init__(self, key):&lt;br /&gt;
         self.key = key&lt;br /&gt;
         self.left = self.right = None&lt;br /&gt;
 &lt;br /&gt;
 root = ...    # Wurzel des Suchbaums&lt;br /&gt;
 nodeFound = treeSearch(root, key)   # None, falls nichts gefunden&lt;br /&gt;
 &lt;br /&gt;
 def treeSearch(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return None&lt;br /&gt;
     elif node.key == key:&lt;br /&gt;
         return node&lt;br /&gt;
     elif key &amp;lt; node.key:&lt;br /&gt;
         return treeSearch(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         return treeSearch(node.right, key)&lt;br /&gt;
&lt;br /&gt;
Daraus resultiert der folgende Suchalgorithmus:&lt;br /&gt;
&lt;br /&gt;
 def treeSort(node,array):     # dynamisches Array als 2. Argument&lt;br /&gt;
     if node is None:     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
         return None:&lt;br /&gt;
     treeSort(node.left, array)     # rekursiv&lt;br /&gt;
     array.append(node.key)     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
     treeSort(node.right, array)     # rekursiv&lt;br /&gt;
&lt;br /&gt;
Komplexität:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 f(N)=\mathcal{O}(1)+f(N_\mathrm{left})+f(N_\mathrm{right})=\mathcal{O}(1)+\mathcal{O}(1)+f(N_\mathrm{leftleft})+f(N_\mathrm{leftright})+\mathcal{O}(1)+f(N_\mathrm{rightleft})&lt;br /&gt;
 +f(N_\mathrm{leftright})=N\ast\mathcal{O}(1)=\mathcal{O}(N)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sortier-Pseudocode:&lt;br /&gt;
&lt;br /&gt;
 Sortieren:&lt;br /&gt;
     (Array) a     # unsortiert&lt;br /&gt;
     (tree) t     # zunächst leer&lt;br /&gt;
 (dynamisches Array) r     # später sortiert&lt;br /&gt;
 for e in a:&lt;br /&gt;
     t = treeInsert(t, e)&lt;br /&gt;
 treeSort(t, r)&lt;br /&gt;
&lt;br /&gt;
== Insert ==&lt;br /&gt;
&lt;br /&gt;
Was passiert wenn der key (Schlüssel) schon vorhanden ist?&lt;br /&gt;
* error (exception)&lt;br /&gt;
* nichts einfügen&lt;br /&gt;
* nichts einfügen aber einen boolean return zurückgeben (einfügen=true, false)&lt;br /&gt;
* nochmals eingefügt (z.B. in der Node Klasse)&lt;br /&gt;
&lt;br /&gt;
Wobei die ersten 3 Punkte zur Mengensemantik gehören und der letzte eine Multimenge ist.&lt;br /&gt;
&lt;br /&gt;
Algorithmus im zweiten Fall (nichts einfügen):&lt;br /&gt;
&lt;br /&gt;
 def treeInsert(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return Node(key)     # Alternative Schreibweise: node = Node(key)&lt;br /&gt;
     if node.key == key:      # und dann:&lt;br /&gt;
         return node          # pass&lt;br /&gt;
     elif key &amp;lt; node.key:     # links im Baum&lt;br /&gt;
         node.left = treeInsert(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         node.right = treeInsert(node.right, key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Remove ==&lt;br /&gt;
Fälle:&lt;br /&gt;
# key (bzw. Knoten der key enthält) ist ein Blatt =&amp;gt; einfach löschen&lt;br /&gt;
# node hat &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; linken Unterbaum oder &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; rechten Unterbaum =&amp;gt; durch Unterbaum ersetzen&lt;br /&gt;
# node hat beide Unterbäume:&lt;br /&gt;
#* Suche Vorgänger: max k &amp;lt; key (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; keys); Vorgänger ist immer ein Fall 1 oder Fall 2&lt;br /&gt;
#*:=&amp;gt; ersetze node durch Vorgänger und entferne Vorgänger&lt;br /&gt;
&lt;br /&gt;
 def treePredecessor(node):     # wird nur bei Fall 3 aufgerufen&lt;br /&gt;
     node = node.left&lt;br /&gt;
     while node.right is not None:&lt;br /&gt;
         node = node.right&lt;br /&gt;
     return node&lt;br /&gt;
 &lt;br /&gt;
 def treeRemove(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return node&lt;br /&gt;
     if key &amp;lt; node.key:&lt;br /&gt;
         node.left = treeRemove(node.left, key)&lt;br /&gt;
     elif key &amp;gt; node.key:&lt;br /&gt;
         node.right = treeRemove(node.right, key)&lt;br /&gt;
     else:&lt;br /&gt;
         if node.left is None and node.right is None:     # Fall 1&lt;br /&gt;
             node = None&lt;br /&gt;
         elif node.left is None:     # Fall 2&lt;br /&gt;
             node = node.right       # +&lt;br /&gt;
         elif node.right is None:    # Fall 2&lt;br /&gt;
             node = node.left&lt;br /&gt;
         else:&lt;br /&gt;
             pred = treePredecessor(node)&lt;br /&gt;
             node.key = pred.key&lt;br /&gt;
             node.left = treeRemove(node.left, pred.key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Komplexitätsanalyse ==&lt;br /&gt;
&lt;br /&gt;
* Pfad (Zwischen node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;): &lt;br /&gt;
**Folge von Knoten (node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt;,...,node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt;), sodass:&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;ki&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;ki+1&amp;lt;/sub&amp;gt; haben eine gemeinsame Kante.&lt;br /&gt;
[[Image:Baum_Pfad.png]]&lt;br /&gt;
(Ein Baum ist ein Graph, indem es zwischen beliebigen Knoten stets genau einen Pfad gibt.)&lt;br /&gt;
&lt;br /&gt;
* Länge eines Pfades: Anzahl der Kanten = Anzahl der Knoten - 1&lt;br /&gt;
* Tiefe eines Knotens: Pfadlänge vom Knoten zur Wurzel des Baumes.&lt;br /&gt;
* Tiefe des Baumes: maximale Tiefe eines Knotens&lt;br /&gt;
* Balance eines Baumes: maximale Tiefe(k) - minimale Tiefe(k) (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; Blätter)&lt;br /&gt;
&lt;br /&gt;
== Ungünstigster Fall von treeSearch ==&lt;br /&gt;
&lt;br /&gt;
Komplexität von treeSearch = Länge des Pfades zum Knoten wo &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; gefunden wird, oder erkannt wird, dass &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; nicht im Baum ist.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; wird nicht gefunden, aber für diese Entscheidung muss der Längste Pfad vollständig durchlaufen werden.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(T)&amp;lt;/math&amp;gt; wo T = Tiefe des Baumes&lt;br /&gt;
*: [1,2,3,4,5]:&amp;lt;br /&amp;gt; [[Image:Balance.png]]&lt;br /&gt;
*: Tiefe T = 4, Balance = 4&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; wo N = Anzahl der Elemente.&lt;br /&gt;
&lt;br /&gt;
== Aufgabe ==&lt;br /&gt;
&lt;br /&gt;
Minimiere Balance (erzeuge balancierten Baum):&lt;br /&gt;
# Einfügen in geschickter Reihenfolge (siehe Übungsaufgabe)&lt;br /&gt;
# Selbstbalancierter Baum:&lt;br /&gt;
#* Überprüfen der Balance nach jedem Einfügen&lt;br /&gt;
#* Umstrukturieren des Baumes, falls Balance &amp;gt; 1 (Suchbaum-Bedingung muss erhalten bleiben)&lt;br /&gt;
#* AVL-Bäume (älteste Variante)&lt;br /&gt;
#* Rot-Schwarz-Bäume (verbreiteste Variante)&lt;br /&gt;
#* Treaps (flexibelste Variante, siehe Übung)&lt;br /&gt;
#* Splay trees&lt;br /&gt;
#* Andersson Trees (einfachste Variante)&lt;br /&gt;
(#* Skip Lists (schnellste Variante, aber kein Binärbaum))&lt;br /&gt;
&lt;br /&gt;
== Umstrukturieren, so dass Suchbaumbedingung erhalten bleibt: ==&lt;br /&gt;
&lt;br /&gt;
Rotation: elementare Umstrukturierungen&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum_Rotation.png]]&lt;br /&gt;
&lt;br /&gt;
 def rotateRight(node):&lt;br /&gt;
     newRoot = node.left&lt;br /&gt;
     node.left = newRoot.right&lt;br /&gt;
     newRoot.right = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
 def rotateLeft(node):&lt;br /&gt;
     newRoot = node.right&lt;br /&gt;
     node.right = newRoot.left&lt;br /&gt;
     newRoot.left = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
== Balance eines Suchbaumes ==&lt;br /&gt;
&lt;br /&gt;
===Balance eines Baumes zu  definieren:===&lt;br /&gt;
[[Image:Abbildung1.jpg|250px|right]]&lt;br /&gt;
*betrachte &amp;lt;tt&amp;gt;None&amp;lt;/tt&amp;gt; als zusätzlichen Knoten : '''sentinel'''   ([[englische Sprache|engl.]] für ''Wächter'')&lt;br /&gt;
*oder defeniere speziellen sentinel-Knoten&lt;br /&gt;
*RS - Pfades : von &amp;lt;tt&amp;gt;root&amp;lt;/tt&amp;gt; &amp;amp;rarr; ''sentinel''&lt;br /&gt;
*Länge eines Pfades ''P''      '''''|P|'''''&lt;br /&gt;
* &amp;lt;u&amp;gt;die Balance eines Baumes zu defenieren:&amp;lt;/u&amp;gt;&lt;br /&gt;
: '''''B = max P '''''&amp;lt;math&amp;gt; \in &amp;lt;/math&amp;gt; '''''{RS}    |P| - min |P|'''''&lt;br /&gt;
:'''''{RS}'''''  Menge aller RS-Pfade&lt;br /&gt;
;vollständiger Baum:  Balance &amp;lt;math&amp;gt;~=0&amp;lt;/math&amp;gt;&lt;br /&gt;
:Alle Knoten(außer Blättern) haben 2 Kinder.&lt;br /&gt;
;perfect balancierter Baum:  Balance  &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 1&lt;br /&gt;
'''alternative Defenition für perfect balancierte Bäume'''&lt;br /&gt;
:Für jeden Teilbaum gilt es: rechtes und linkes Kind (für jeden Knoten) sind auch wieder perfect balancierte Bäume und ihre Höhe unterscheidet sich höchstens um '''1'''.&lt;br /&gt;
&lt;br /&gt;
===Größe===&lt;br /&gt;
[[Image:baum-v.png|700px|right]]&lt;br /&gt;
====Größe des vollständigen Baumes====&lt;br /&gt;
Ebene K hat 2 &amp;lt;sup&amp;gt;k&amp;lt;/sup&amp;gt; Knoten.&lt;br /&gt;
Falls Tiefe = d , dann N = 2&amp;lt;sup&amp;gt;0&amp;lt;/sup&amp;gt; + 2&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;.....+ 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; = 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1&lt;br /&gt;
&lt;br /&gt;
====Größe des perfect balancierten Baumes====&lt;br /&gt;
Die Tiefe d, kann nicht besser sein als vollständiger Baum&lt;br /&gt;
:'''=&amp;gt;'''N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2 &amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
;schlechteste perfect balancierter Baum : ist ein vollständiger der Tiefe  d - 1 + 1, N &amp;lt;math&amp;gt; \ge&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:'''=&amp;gt;'''  2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1)   &amp;lt;math&amp;gt;~&amp;lt;&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt;)&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
d &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt;  log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; (d + 1)&lt;br /&gt;
&lt;br /&gt;
*Ergibt die Komplexität der Suche im schlechtesten Fall: Anzahl der Vergleiche pro Knoten( = 2 bzw. = 3)&amp;lt;math&amp;gt;\ast&amp;lt;/math&amp;gt;Anzahl der Knoten&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow ~f(N)\le 2d \le 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
*'''perfekt balancierter Baum '''=&amp;gt; AVL-Baum &lt;br /&gt;
*'''balancierter Baum''':&lt;br /&gt;
::&amp;lt;math&amp;gt;\forall  N:d(N)\le c \ast d (N)&amp;lt;/math&amp;gt; und  &amp;lt;math&amp;gt;1 \le c &amp;lt; \mathcal {1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:: d ist die Tiefe von perfekt balancierten Baum&lt;br /&gt;
&lt;br /&gt;
:''Komplexität der Suche'':&amp;lt;math&amp;gt; ~f(N)\le  c\ast 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
:algorithmisch einfacher als perfekt balancierter Baum, aber fast genauso schnell&lt;br /&gt;
&lt;br /&gt;
====Selbst-balancierter Baum====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  def insertTree(node,key):&lt;br /&gt;
    if node is sentinel:       #(None = sentinel)&lt;br /&gt;
        return Node(key)       &lt;br /&gt;
    if node.key == key:&lt;br /&gt;
        return Node&lt;br /&gt;
    if key &amp;lt; node.key:&lt;br /&gt;
        Node = insertTree(node.left, key)&lt;br /&gt;
    else:&lt;br /&gt;
        node.right = insertTree(node.right, key)&lt;br /&gt;
   #optimiere Balance:&lt;br /&gt;
        return node&lt;br /&gt;
&lt;br /&gt;
===Anderson-Bäume===&lt;br /&gt;
&lt;br /&gt;
  class Node:&lt;br /&gt;
    def__init__(self, key):&lt;br /&gt;
        self.key = key&lt;br /&gt;
        self.left = selft.right = None&lt;br /&gt;
        #einfügen:&lt;br /&gt;
        self.level = 1&lt;br /&gt;
&lt;br /&gt;
:''level'' : kodiert Abstand von sentinel&lt;br /&gt;
====Regeln====&lt;br /&gt;
Es gibt vertikale Kanten(parent.level == child.level + 1 ) und horizontale Kanten(parent.level == child.level)&lt;br /&gt;
:*verfeinerter Regel :  ''level kodiert reduzierten Abstand von Setinel''  (d.b. horizontale Kanten werden nicht gezählt)&lt;br /&gt;
::(die nächste zwei Regeln sichern die Balance):&lt;br /&gt;
::*alle RS-Pfade haben die gleiche reduzierte Länge oder ''root'' hat bei allen Pfaden das gleiche Level&lt;br /&gt;
::*kein Pfad hat 2 aufeinanderer folgende horizontale Kanten&lt;br /&gt;
:*nur Kanten zum rechten Kind dürfen horizontal sein&lt;br /&gt;
:*die reduzierte Höhe jedes Blatts ist '''hr=1'''&lt;br /&gt;
&lt;br /&gt;
====Beweis====&lt;br /&gt;
Vereinfachung des Algorithmus&lt;br /&gt;
:'''Satz''':ein Anderson-Baum ist balanciert.&lt;br /&gt;
*1. Sei '''hr'''- die reduzierte Höhe&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; jeder Teilbaum enthält mindestens &amp;lt;math&amp;gt;N\ge 2^{hr} -1 &amp;lt;/math&amp;gt;  Knoten&lt;br /&gt;
&lt;br /&gt;
::a). Blätter : reduzierte Höhe 1 =&amp;gt; &amp;lt;math&amp;gt; N\ge 2^{1} - 1 = 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
::b). inneren Knoten: jeder Unterbaum hat mindestens reduzierte Höhe &amp;lt;math&amp;gt; ~hr - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt;  jeder Unterbaum hat mindestens &amp;lt;math&amp;gt; ~2^{hr} -1 &amp;lt;/math&amp;gt; Knoten&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt;N \ge 2 (2^{hr-1} -1)+1 = 2^w - 2 + 1 = 2^{hr} -1&amp;lt;/math&amp;gt;&lt;br /&gt;
::::alle RS-Pfade heben gleiche Länge&lt;br /&gt;
[[Image:Anderson.png|700px|right]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
*2. Kein Pfad hat 2 aufeinanderfolgende horizontal Kanten&lt;br /&gt;
:::&amp;lt;math&amp;gt;\Rightarrow  d \le 2 hr + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
*3. zusammen:&lt;br /&gt;
:&amp;lt;math&amp;gt;N \ge 2^{\frac {d}{2}} - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;log_{2}{ N}+1\ge log 2^{\frac {d}{2}} - 1 \ge \frac {d}{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; ~d &amp;lt; 2 log_{2}{(N + 1)}&amp;lt;/math&amp;gt;     - balancierter Baum&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
:'''Suchzeit''' : &amp;lt;math&amp;gt; ~ f(N) = \mathcal{O}(log{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Wie erreicht man die Balance===&lt;br /&gt;
====Rotation====&lt;br /&gt;
[[Image:Abbildung4.jpg|text-top|350px|Illustration von Rotation]]&lt;br /&gt;
&lt;br /&gt;
   def rotateRight (node):&lt;br /&gt;
      root = node.left&lt;br /&gt;
      node.left = node.right&lt;br /&gt;
      root.right = node&lt;br /&gt;
      return root&lt;br /&gt;
   def rotateLeft(node):&lt;br /&gt;
      root = node.right&lt;br /&gt;
      node.right = node.left&lt;br /&gt;
      root.left = node&lt;br /&gt;
      return root&lt;br /&gt;
&lt;br /&gt;
====Optimierung der Balance====&lt;br /&gt;
   &lt;br /&gt;
&lt;br /&gt;
       if node.left is not sentinel and node.level==node.left.level:&lt;br /&gt;
            node &amp;gt; rotateRight(node)&lt;br /&gt;
       if node.right is not sentinel and node.right.right is  not sentinel and node.level==rotate.right.right.level:&lt;br /&gt;
            node = rotateLeft(node)&lt;br /&gt;
            node.level += 1&lt;br /&gt;
[[Image:rotate.jpg|text-top]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
[[Image:liste.png]]&lt;/div&gt;</summary>
		<author><name>Nata</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=File:Liste.png&amp;diff=1507</id>
		<title>File:Liste.png</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=File:Liste.png&amp;diff=1507"/>
				<updated>2008-05-29T00:34:22Z</updated>
		
		<summary type="html">&lt;p&gt;Nata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Nata</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1506</id>
		<title>Suchen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1506"/>
				<updated>2008-05-29T00:34:00Z</updated>
		
		<summary type="html">&lt;p&gt;Nata: /* Optimierung der Balance */&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 = sequentialSearch(a, key) &lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq &amp;lt;/math&amp;gt; foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def sequentialSearch(a, key):&lt;br /&gt;
    for i in range(len(a)):&lt;br /&gt;
        if a[i] == key:  # bzw. allgemeiner a[i].key == key &lt;br /&gt;
            return i&lt;br /&gt;
    return -1&lt;br /&gt;
&lt;br /&gt;
Wir wollen jetzt die Komplexität dieses Algorithmus bestimmen, wobei die Problemgröße durch &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt; gegeben ist. &lt;br /&gt;
&lt;br /&gt;
Dabei nimmt man an, dass der innerste Vergleich (a[i] == key) jeweils &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt; ist (diese Annahme könnte verletzt sein, wenn der Vergleichsoperator überladen ist und dadurch eine höhere Komplexität hat). Dieser Vergleich wird in der for-Schleife jeweils N-mal durchgeführt (&amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt;), so dass man nach der Verschachtelungsregel eine gesamte Komplexität von &amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt; erhält.&lt;br /&gt;
&lt;br /&gt;
Der Name ''lineare'' Suche rührt von diesem linearen Anwachsen der Komplexität mit der Arraygröße her.&lt;br /&gt;
&lt;br /&gt;
==Binäre Suche==&lt;br /&gt;
&lt;br /&gt;
Wie wir weiter unten zeigen werden, gestattet es diese Suchmethode, die Gesamtdauer der Suche in großen Datensätzen beträchtlich zu verringern. Die Methode beruht auf dem [http://de.wikipedia.org/wiki/Divide_and_Conquer Divide and Conquer-Prinzip], wobei die Suche in jedem Schritt rekursiv auf eine Hälfte des Datensatzes eingeschränkt wird. Weitere Details zur Methode sind [http://de.wikipedia.org/wiki/Bin%C3%A4re_Suche hier] zu finden. &lt;br /&gt;
&lt;br /&gt;
Die Methode ist nur dann anwendbar beziehungsweise effektiv, wenn folgendes gilt:&lt;br /&gt;
&lt;br /&gt;
# Auf der Eigenschaft der Daten, die zur Suche verwendet wird, ist eine Ordnung im Sinne von &amp;lt; oder &amp;gt; definiert.&lt;br /&gt;
# Wir wollen uns auf Datensätze beschränken, die schon fertig aufgebaut sind, in die also keine neuen Elemente mehr eingefügt werden, wenn man mit dem Suchen beginnt. Ist dies nicht der Fall, so ist unter Umständen die Implementierung über einen [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum Binärbaum] (siehe auch weiter unten) geschickter. &lt;br /&gt;
&lt;br /&gt;
Der folgende Algorithmus zeigt eine beispielhafte Implementierung der Methode (wir setzen wieder &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt;):&lt;br /&gt;
&lt;br /&gt;
 a = [...,...]     # array&lt;br /&gt;
 a.sort()   # sortiere über Ordnung des Schlüssels&lt;br /&gt;
 foundIndex = binSearch(a, key, 0, len(a))  # (Array, Schlüssel, von wo bis wo suchen im Array)&lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq&amp;lt;/math&amp;gt;  foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def binSearch(a, key, start, end):  # start ist 1. Index, end ist letzter Index + 1&lt;br /&gt;
    size = end - start   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if size &amp;lt;= 0:   # Bereich leer?  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return -1   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    center = (start + end)/2   # Integer Division, Ergebnis wird abgerundet, wichtig für ganzzahlige Indizes &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if a[center] == key:  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return center  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    elif a[center] &amp;lt; key:  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return binSearch(a, key, center + 1, end)  # Rekursion&lt;br /&gt;
    else:&lt;br /&gt;
        return binSearch(a, key, start + 1, center)  # Rekursion&lt;br /&gt;
&lt;br /&gt;
Zur Berechnung der Komplexität dieses Algorithmus vernachlässigen wir zunächst den Aufwand, den die Sortierung weiter oben verursacht. Dieser Schritt mag oder mag nicht zulässig sein.&lt;br /&gt;
&lt;br /&gt;
Nach der Sequenzregel haben auch alle &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;.  Es bleibt die Komplexität der Rekursion zu berechnen. Die gesamte Komplexität des Algorithmus (jetzt als Funktion f bezeichnet) setzt sich zusammen aus den oben erwähnten &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen sowie der Rekursion und ist &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f(N) = \mathcal{O}(1) + f(N/2) = \mathcal{O}(1) + \mathcal{O}(1) + f(N/4) = ... = \underbrace{\mathcal{O}(1) + ... + \mathcal{O}(1) + \underbrace{f(0)}_{\mathcal{O}(1)\, \rightarrow \,\mathrm{size-Abfrage}}}_{n+1 \,\mathrm{Terme}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Falls jetzt gilt &amp;lt;math&amp;gt; N = 2^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \rightarrow f(N) = \mathcal{O}(1) \cdot \mathcal{O}(n+1) = \mathcal{O}(n) = \mathcal{O}(\lg N) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Für große Datenmengen ist die ''binäre Suche'' also weit effizienter als die ''lineare Suche''. Verdoppelt sich beispielsweise die zu durchsuchende Datenmenge, so verdoppelt sich der Aufwand für die ''sequentielle Suche'' - bei der ''binären Suche'' hingegen benötigt man lediglich eine zusätzliche Vergleichsoperation. &lt;br /&gt;
&lt;br /&gt;
Für kleine Daten (&amp;lt;math&amp;gt; N = 4,\, 5 &amp;lt;/math&amp;gt;) ist die ''sequentielle Suche'' jedoch schneller als die ''binäre Suche'', da hier die rekursiven Funktionsaufrufe teurer als das Mehr an Vergleichen sind. Ein anderer ungünstiger Fall ist gegeben, wenn nur sehr wenige Suchanfragen erfolgen (weniger als &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; viele). Dann wird der Aufwand durch das Sortieren des Arrays dominiert, ist also &amp;lt;math&amp;gt;\mathcal{O}(N \lg N) &amp;lt;/math&amp;gt;. Auch dann ist sequentielle Suche vorzuziehen.&lt;br /&gt;
&lt;br /&gt;
Eine relativ einfache Möglichkeit, die ''binäre Suche'' zu verbessern, ist die sogenannte ''Interpolationssuche''. Hierbei wird die neue Position für die Suche, also die Mitte des Arrays, durch eine Schätzung ersetzt, die angibt, wo sich der Schlüssel innerhalb des Arrays befinden könnte. Bei der Suche in einem Telefonbuch nach dem Namen Zebra würde man ja auch nicht in der Mitte anfangen. Näheres hierzu im Buch von ''Sedgewick''.&lt;br /&gt;
&lt;br /&gt;
Um sich den Algorithmus der ''binären Suche'' klar zu machen, ist es instruktiv, sich die folgende Tabelle genauer anzusehen, die die sukzessive Belegung der Variablen bei verschiedenen Anfragen beschreibt. Die Testfälle wurden nach dem Prinzip des ''domain partitioning'' gewählt. Das zugehörige Array hat die Einträge&lt;br /&gt;
&lt;br /&gt;
 a = [2, 3, 4, 5, 6]&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align:center&amp;quot; border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;5&amp;quot; cellspacing=&amp;quot;0&amp;quot; &lt;br /&gt;
! gesuchter key   !!  start      !! end  !! size !! center !! return &amp;lt;br/&amp;gt; (-1 oder index)  !! Kommentare  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 4     ||0            || 5    ||  5   || 2   ||  2         || gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 2     || 0           || 5    ||  5   || 2   ||            || linker Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||           &lt;br /&gt;
|-&lt;br /&gt;
|       ||  0          || 1    ||  1   || 0   ||  0         || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 1     ||0            || 5    ||  5   || 2   ||            || links außerhalb&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 1    ||  1   || 0   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 0    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 6     ||0            || 5    ||  5   || 2   ||            || rechter Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    || 2    || 4   || 4          || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 5     ||0            || 5    ||  5   || 2   ||            || typischer Fall&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||3            || 5    ||  2   || 4   ||            ||  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       || 3           || 4    ||  1   || 3   || 3          || gefunden&lt;br /&gt;
|- &lt;br /&gt;
| 7     ||0            || 5    ||  5   || 2   ||            || rechts außerhalb        &lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    ||  2   || 4   ||            ||&lt;br /&gt;
|-&lt;br /&gt;
|       ||5            || 5    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Suche in einem Binärbaum ==&lt;br /&gt;
&lt;br /&gt;
Eine kurze Einführung in Binärbäume findet man [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum hier].&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum.png|text-top|300x300px|Zur Illustration von Bäumen]]&lt;br /&gt;
&lt;br /&gt;
Bäume sind zweidimensional verkettete Strukturen. Sie gehören zu den fundamentalen Datenstrukturen in der Informatik. Da man in Bäumen nicht nur Daten speichern kann, sondern auch relevante Beziehungen der Daten untereinander, festgelegt über eine Ordnung auf der vergleichenden Dateneigenschaft (''Schlüssel''), eignen sich Bäume also insbesondere, um gesuchte Daten schnell wieder auffinden zu können.&lt;br /&gt;
&lt;br /&gt;
Ein ''Binärbaum'' wie oben skizziert besteht aus einer Menge von ''Knoten'', die untereinander durch ''Kanten'' verbunden sind. Jeder Knoten hat einen linken und einen rechten Unterbaum, der auch leer sein kann (in Python ließe sich dies mit ''None'' implementieren). Führt eine Kante von Knoten A zu Knoten B, so heißt A Vater von B und B Kind von A. Es gibt genau einen Knoten ohne Vater, den man ''Wurzel'' nennt. Knoten ohne Kinder heißen ''Blätter''.&lt;br /&gt;
&lt;br /&gt;
Ein ''Suchbaum'' hat zusätzlich die Eigenschaft, dass die Schlüssel jedes Knotens sortiert sind. Alle Schlüssel im linken Unterbaum sind kleiner, alle Schlüssel im rechten Unterbaum sind größer als ihr Vater. Wir wollen hierbei annehmen, dass jeder Schlüssel pro Datensatz nur einmal vorkommt, da sich sonst die &amp;gt;- oder &amp;lt;-Relation nicht mehr strikt erfüllen ließe.&lt;br /&gt;
&lt;br /&gt;
Um in einem Baum suchen zu können, wollen wir von zwei Annahmen ausgehen:&lt;br /&gt;
# Einfügen und Suchen im Baum wechseln sich ab. (Wenn das Suchen erst beginnt, nachdem alle Einfügungen erfolgt sind, wäre ein dynamisches Array mit binärer Suche wie oben wesentlich einfacher.)&lt;br /&gt;
# Der Schlüssel, der die Anordnung bestimmt, kennt eine [http://de.wikipedia.org/wiki/Ordnungsrelation Ordnung] (&amp;lt;-Relation oder &amp;gt;-Relation).&lt;br /&gt;
&lt;br /&gt;
Der folgende Python-Code zeigt beispielhaft, wie man in einem Suchbaum suchen könnte. Der Konstruktor für einen Knoten des Suchbaums ließe sich zum Beispiel so implementieren:&lt;br /&gt;
 &lt;br /&gt;
 class Node:&lt;br /&gt;
     def __init__(self, key):&lt;br /&gt;
         self.key = key&lt;br /&gt;
         self.left = self.right = None&lt;br /&gt;
 &lt;br /&gt;
 root = ...    # Wurzel des Suchbaums&lt;br /&gt;
 nodeFound = treeSearch(root, key)   # None, falls nichts gefunden&lt;br /&gt;
 &lt;br /&gt;
 def treeSearch(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return None&lt;br /&gt;
     elif node.key == key:&lt;br /&gt;
         return node&lt;br /&gt;
     elif key &amp;lt; node.key:&lt;br /&gt;
         return treeSearch(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         return treeSearch(node.right, key)&lt;br /&gt;
&lt;br /&gt;
Daraus resultiert der folgende Suchalgorithmus:&lt;br /&gt;
&lt;br /&gt;
 def treeSort(node,array):     # dynamisches Array als 2. Argument&lt;br /&gt;
     if node is None:     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
         return None:&lt;br /&gt;
     treeSort(node.left, array)     # rekursiv&lt;br /&gt;
     array.append(node.key)     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
     treeSort(node.right, array)     # rekursiv&lt;br /&gt;
&lt;br /&gt;
Komplexität:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 f(N)=\mathcal{O}(1)+f(N_\mathrm{left})+f(N_\mathrm{right})=\mathcal{O}(1)+\mathcal{O}(1)+f(N_\mathrm{leftleft})+f(N_\mathrm{leftright})+\mathcal{O}(1)+f(N_\mathrm{rightleft})&lt;br /&gt;
 +f(N_\mathrm{leftright})=N\ast\mathcal{O}(1)=\mathcal{O}(N)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sortier-Pseudocode:&lt;br /&gt;
&lt;br /&gt;
 Sortieren:&lt;br /&gt;
     (Array) a     # unsortiert&lt;br /&gt;
     (tree) t     # zunächst leer&lt;br /&gt;
 (dynamisches Array) r     # später sortiert&lt;br /&gt;
 for e in a:&lt;br /&gt;
     t = treeInsert(t, e)&lt;br /&gt;
 treeSort(t, r)&lt;br /&gt;
&lt;br /&gt;
== Insert ==&lt;br /&gt;
&lt;br /&gt;
Was passiert wenn der key (Schlüssel) schon vorhanden ist?&lt;br /&gt;
* error (exception)&lt;br /&gt;
* nichts einfügen&lt;br /&gt;
* nichts einfügen aber einen boolean return zurückgeben (einfügen=true, false)&lt;br /&gt;
* nochmals eingefügt (z.B. in der Node Klasse)&lt;br /&gt;
&lt;br /&gt;
Wobei die ersten 3 Punkte zur Mengensemantik gehören und der letzte eine Multimenge ist.&lt;br /&gt;
&lt;br /&gt;
Algorithmus im zweiten Fall (nichts einfügen):&lt;br /&gt;
&lt;br /&gt;
 def treeInsert(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return Node(key)     # Alternative Schreibweise: node = Node(key)&lt;br /&gt;
     if node.key == key:      # und dann:&lt;br /&gt;
         return node          # pass&lt;br /&gt;
     elif key &amp;lt; node.key:     # links im Baum&lt;br /&gt;
         node.left = treeInsert(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         node.right = treeInsert(node.right, key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Remove ==&lt;br /&gt;
Fälle:&lt;br /&gt;
# key (bzw. Knoten der key enthält) ist ein Blatt =&amp;gt; einfach löschen&lt;br /&gt;
# node hat &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; linken Unterbaum oder &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; rechten Unterbaum =&amp;gt; durch Unterbaum ersetzen&lt;br /&gt;
# node hat beide Unterbäume:&lt;br /&gt;
#* Suche Vorgänger: max k &amp;lt; key (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; keys); Vorgänger ist immer ein Fall 1 oder Fall 2&lt;br /&gt;
#*:=&amp;gt; ersetze node durch Vorgänger und entferne Vorgänger&lt;br /&gt;
&lt;br /&gt;
 def treePredecessor(node):     # wird nur bei Fall 3 aufgerufen&lt;br /&gt;
     node = node.left&lt;br /&gt;
     while node.right is not None:&lt;br /&gt;
         node = node.right&lt;br /&gt;
     return node&lt;br /&gt;
 &lt;br /&gt;
 def treeRemove(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return node&lt;br /&gt;
     if key &amp;lt; node.key:&lt;br /&gt;
         node.left = treeRemove(node.left, key)&lt;br /&gt;
     elif key &amp;gt; node.key:&lt;br /&gt;
         node.right = treeRemove(node.right, key)&lt;br /&gt;
     else:&lt;br /&gt;
         if node.left is None and node.right is None:     # Fall 1&lt;br /&gt;
             node = None&lt;br /&gt;
         elif node.left is None:     # Fall 2&lt;br /&gt;
             node = node.right       # +&lt;br /&gt;
         elif node.right is None:    # Fall 2&lt;br /&gt;
             node = node.left&lt;br /&gt;
         else:&lt;br /&gt;
             pred = treePredecessor(node)&lt;br /&gt;
             node.key = pred.key&lt;br /&gt;
             node.left = treeRemove(node.left, pred.key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Komplexitätsanalyse ==&lt;br /&gt;
&lt;br /&gt;
* Pfad (Zwischen node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;): &lt;br /&gt;
**Folge von Knoten (node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt;,...,node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt;), sodass:&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;ki&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;ki+1&amp;lt;/sub&amp;gt; haben eine gemeinsame Kante.&lt;br /&gt;
[[Image:Baum_Pfad.png]]&lt;br /&gt;
(Ein Baum ist ein Graph, indem es zwischen beliebigen Knoten stets genau einen Pfad gibt.)&lt;br /&gt;
&lt;br /&gt;
* Länge eines Pfades: Anzahl der Kanten = Anzahl der Knoten - 1&lt;br /&gt;
* Tiefe eines Knotens: Pfadlänge vom Knoten zur Wurzel des Baumes.&lt;br /&gt;
* Tiefe des Baumes: maximale Tiefe eines Knotens&lt;br /&gt;
* Balance eines Baumes: maximale Tiefe(k) - minimale Tiefe(k) (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; Blätter)&lt;br /&gt;
&lt;br /&gt;
== Ungünstigster Fall von treeSearch ==&lt;br /&gt;
&lt;br /&gt;
Komplexität von treeSearch = Länge des Pfades zum Knoten wo &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; gefunden wird, oder erkannt wird, dass &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; nicht im Baum ist.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; wird nicht gefunden, aber für diese Entscheidung muss der Längste Pfad vollständig durchlaufen werden.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(T)&amp;lt;/math&amp;gt; wo T = Tiefe des Baumes&lt;br /&gt;
*: [1,2,3,4,5]:&amp;lt;br /&amp;gt; [[Image:Balance.png]]&lt;br /&gt;
*: Tiefe T = 4, Balance = 4&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; wo N = Anzahl der Elemente.&lt;br /&gt;
&lt;br /&gt;
== Aufgabe ==&lt;br /&gt;
&lt;br /&gt;
Minimiere Balance (erzeuge balancierten Baum):&lt;br /&gt;
# Einfügen in geschickter Reihenfolge (siehe Übungsaufgabe)&lt;br /&gt;
# Selbstbalancierter Baum:&lt;br /&gt;
#* Überprüfen der Balance nach jedem Einfügen&lt;br /&gt;
#* Umstrukturieren des Baumes, falls Balance &amp;gt; 1 (Suchbaum-Bedingung muss erhalten bleiben)&lt;br /&gt;
#* AVL-Bäume (älteste Variante)&lt;br /&gt;
#* Rot-Schwarz-Bäume (verbreiteste Variante)&lt;br /&gt;
#* Treaps (flexibelste Variante, siehe Übung)&lt;br /&gt;
#* Splay trees&lt;br /&gt;
#* Andersson Trees (einfachste Variante)&lt;br /&gt;
(#* Skip Lists (schnellste Variante, aber kein Binärbaum))&lt;br /&gt;
&lt;br /&gt;
== Umstrukturieren, so dass Suchbaumbedingung erhalten bleibt: ==&lt;br /&gt;
&lt;br /&gt;
Rotation: elementare Umstrukturierungen&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum_Rotation.png]]&lt;br /&gt;
&lt;br /&gt;
 def rotateRight(node):&lt;br /&gt;
     newRoot = node.left&lt;br /&gt;
     node.left = newRoot.right&lt;br /&gt;
     newRoot.right = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
 def rotateLeft(node):&lt;br /&gt;
     newRoot = node.right&lt;br /&gt;
     node.right = newRoot.left&lt;br /&gt;
     newRoot.left = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
== Balance eines Suchbaumes ==&lt;br /&gt;
&lt;br /&gt;
===Balance eines Baumes zu  definieren:===&lt;br /&gt;
[[Image:Abbildung1.jpg|250px|right]]&lt;br /&gt;
*betrachte &amp;lt;tt&amp;gt;None&amp;lt;/tt&amp;gt; als zusätzlichen Knoten : '''sentinel'''   ([[englische Sprache|engl.]] für ''Wächter'')&lt;br /&gt;
*oder defeniere speziellen sentinel-Knoten&lt;br /&gt;
*RS - Pfades : von &amp;lt;tt&amp;gt;root&amp;lt;/tt&amp;gt; &amp;amp;rarr; ''sentinel''&lt;br /&gt;
*Länge eines Pfades ''P''      '''''|P|'''''&lt;br /&gt;
* &amp;lt;u&amp;gt;die Balance eines Baumes zu defenieren:&amp;lt;/u&amp;gt;&lt;br /&gt;
: '''''B = max P '''''&amp;lt;math&amp;gt; \in &amp;lt;/math&amp;gt; '''''{RS}    |P| - min |P|'''''&lt;br /&gt;
:'''''{RS}'''''  Menge aller RS-Pfade&lt;br /&gt;
;vollständiger Baum:  Balance &amp;lt;math&amp;gt;~=0&amp;lt;/math&amp;gt;&lt;br /&gt;
:Alle Knoten(außer Blättern) haben 2 Kinder.&lt;br /&gt;
;perfect balancierter Baum:  Balance  &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 1&lt;br /&gt;
'''alternative Defenition für perfect balancierte Bäume'''&lt;br /&gt;
:Für jeden Teilbaum gilt es: rechtes und linkes Kind (für jeden Knoten) sind auch wieder perfect balancierte Bäume und ihre Höhe unterscheidet sich höchstens um '''1'''.&lt;br /&gt;
&lt;br /&gt;
===Größe===&lt;br /&gt;
[[Image:baum-v.png|700px|right]]&lt;br /&gt;
====Größe des vollständigen Baumes====&lt;br /&gt;
Ebene K hat 2 &amp;lt;sup&amp;gt;k&amp;lt;/sup&amp;gt; Knoten.&lt;br /&gt;
Falls Tiefe = d , dann N = 2&amp;lt;sup&amp;gt;0&amp;lt;/sup&amp;gt; + 2&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;.....+ 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; = 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1&lt;br /&gt;
&lt;br /&gt;
====Größe des perfect balancierten Baumes====&lt;br /&gt;
Die Tiefe d, kann nicht besser sein als vollständiger Baum&lt;br /&gt;
:'''=&amp;gt;'''N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2 &amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
;schlechteste perfect balancierter Baum : ist ein vollständiger der Tiefe  d - 1 + 1, N &amp;lt;math&amp;gt; \ge&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:'''=&amp;gt;'''  2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1)   &amp;lt;math&amp;gt;~&amp;lt;&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt;)&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
d &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt;  log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; (d + 1)&lt;br /&gt;
&lt;br /&gt;
*Ergibt die Komplexität der Suche im schlechtesten Fall: Anzahl der Vergleiche pro Knoten( = 2 bzw. = 3)&amp;lt;math&amp;gt;\ast&amp;lt;/math&amp;gt;Anzahl der Knoten&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow ~f(N)\le 2d \le 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
*'''perfekt balancierter Baum '''=&amp;gt; AVL-Baum &lt;br /&gt;
*'''balancierter Baum''':&lt;br /&gt;
::&amp;lt;math&amp;gt;\forall  N:d(N)\le c \ast d (N)&amp;lt;/math&amp;gt; und  &amp;lt;math&amp;gt;1 \le c &amp;lt; \mathcal {1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:: d ist die Tiefe von perfekt balancierten Baum&lt;br /&gt;
&lt;br /&gt;
:''Komplexität der Suche'':&amp;lt;math&amp;gt; ~f(N)\le  c\ast 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
:algorithmisch einfacher als perfekt balancierter Baum, aber fast genauso schnell&lt;br /&gt;
&lt;br /&gt;
====Selbst-balancierter Baum====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  def insertTree(node,key):&lt;br /&gt;
    if node is sentinel:       #(None = sentinel)&lt;br /&gt;
        return Node(key)       &lt;br /&gt;
    if node.key == key:&lt;br /&gt;
        return Node&lt;br /&gt;
    if key &amp;lt; node.key:&lt;br /&gt;
        Node = insertTree(node.left, key)&lt;br /&gt;
    else:&lt;br /&gt;
        node.right = insertTree(node.right, key)&lt;br /&gt;
   #optimiere Balance:&lt;br /&gt;
        return node&lt;br /&gt;
&lt;br /&gt;
===Anderson-Bäume===&lt;br /&gt;
&lt;br /&gt;
  class Node:&lt;br /&gt;
    def__init__(self, key):&lt;br /&gt;
        self.key = key&lt;br /&gt;
        self.left = selft.right = None&lt;br /&gt;
        #einfügen:&lt;br /&gt;
        self.level = 1&lt;br /&gt;
&lt;br /&gt;
:''level'' : kodiert Abstand von sentinel&lt;br /&gt;
====Regeln====&lt;br /&gt;
Es gibt vertikale Kanten(parent.level == child.level + 1 ) und horizontale Kanten(parent.level == child.level)&lt;br /&gt;
:*verfeinerter Regel :  ''level kodiert reduzierten Abstand von Setinel''  (d.b. horizontale Kanten werden nicht gezählt)&lt;br /&gt;
::(die nächste zwei Regeln sichern die Balance):&lt;br /&gt;
::*alle RS-Pfade haben die gleiche reduzierte Länge oder ''root'' hat bei allen Pfaden das gleiche Level&lt;br /&gt;
::*kein Pfad hat 2 aufeinanderer folgende horizontale Kanten&lt;br /&gt;
:*nur Kanten zum rechten Kind dürfen horizontal sein&lt;br /&gt;
:*die reduzierte Höhe jedes Blatts ist '''hr=1'''&lt;br /&gt;
&lt;br /&gt;
====Beweis====&lt;br /&gt;
Vereinfachung des Algorithmus&lt;br /&gt;
:'''Satz''':ein Anderson-Baum ist balanciert.&lt;br /&gt;
*1. Sei '''hr'''- die reduzierte Höhe&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; jeder Teilbaum enthält mindestens &amp;lt;math&amp;gt;N\ge 2^{hr} -1 &amp;lt;/math&amp;gt;  Knoten&lt;br /&gt;
&lt;br /&gt;
::a). Blätter : reduzierte Höhe 1 =&amp;gt; &amp;lt;math&amp;gt; N\ge 2^{1} - 1 = 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
::b). inneren Knoten: jeder Unterbaum hat mindestens reduzierte Höhe &amp;lt;math&amp;gt; ~hr - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt;  jeder Unterbaum hat mindestens &amp;lt;math&amp;gt; ~2^{hr} -1 &amp;lt;/math&amp;gt; Knoten&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt;N \ge 2 (2^{hr-1} -1)+1 = 2^w - 2 + 1 = 2^{hr} -1&amp;lt;/math&amp;gt;&lt;br /&gt;
::::alle RS-Pfade heben gleiche Länge&lt;br /&gt;
[[Image:Anderson.png|700px|right]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
*2. Kein Pfad hat 2 aufeinanderfolgende horizontal Kanten&lt;br /&gt;
:::&amp;lt;math&amp;gt;\Rightarrow  d \le 2 hr + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
*3. zusammen:&lt;br /&gt;
:&amp;lt;math&amp;gt;N \ge 2^{\frac {d}{2}} - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;log_{2}{ N}+1\ge log 2^{\frac {d}{2}} - 1 \ge \frac {d}{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; ~d &amp;lt; 2 log_{2}{(N + 1)}&amp;lt;/math&amp;gt;     - balancierter Baum&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
:'''Suchzeit''' : &amp;lt;math&amp;gt; ~ f(N) = \mathcal{O}(log{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Wie erreicht man die Balance===&lt;br /&gt;
====Rotation====&lt;br /&gt;
[[Image:Abbildung4.jpg|text-top|350px|Illustration von Rotation]]&lt;br /&gt;
&lt;br /&gt;
   def rotateRight (node):&lt;br /&gt;
      root = node.left&lt;br /&gt;
      node.left = node.right&lt;br /&gt;
      root.right = node&lt;br /&gt;
      return root&lt;br /&gt;
   def rotateLeft(node):&lt;br /&gt;
      root = node.right&lt;br /&gt;
      node.right = node.left&lt;br /&gt;
      root.left = node&lt;br /&gt;
      return root&lt;br /&gt;
&lt;br /&gt;
====Optimierung der Balance====&lt;br /&gt;
   &lt;br /&gt;
&lt;br /&gt;
       if node.left is not sentinel and node.level==node.left.level:&lt;br /&gt;
            node &amp;gt; rotateRight(node)&lt;br /&gt;
       if node.right is not sentinel and node.right.right is  not sentinel and node.level==rotate.right.right.level:&lt;br /&gt;
            node = rotateLeft(node)&lt;br /&gt;
            node.level += 1&lt;br /&gt;
[[Image:rotate.jpg|text-top]]&lt;br /&gt;
[[Image:liste.png]]&lt;/div&gt;</summary>
		<author><name>Nata</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1505</id>
		<title>Suchen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1505"/>
				<updated>2008-05-29T00:32:37Z</updated>
		
		<summary type="html">&lt;p&gt;Nata: /* Optimierung der Balance */&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 = sequentialSearch(a, key) &lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq &amp;lt;/math&amp;gt; foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def sequentialSearch(a, key):&lt;br /&gt;
    for i in range(len(a)):&lt;br /&gt;
        if a[i] == key:  # bzw. allgemeiner a[i].key == key &lt;br /&gt;
            return i&lt;br /&gt;
    return -1&lt;br /&gt;
&lt;br /&gt;
Wir wollen jetzt die Komplexität dieses Algorithmus bestimmen, wobei die Problemgröße durch &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt; gegeben ist. &lt;br /&gt;
&lt;br /&gt;
Dabei nimmt man an, dass der innerste Vergleich (a[i] == key) jeweils &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt; ist (diese Annahme könnte verletzt sein, wenn der Vergleichsoperator überladen ist und dadurch eine höhere Komplexität hat). Dieser Vergleich wird in der for-Schleife jeweils N-mal durchgeführt (&amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt;), so dass man nach der Verschachtelungsregel eine gesamte Komplexität von &amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt; erhält.&lt;br /&gt;
&lt;br /&gt;
Der Name ''lineare'' Suche rührt von diesem linearen Anwachsen der Komplexität mit der Arraygröße her.&lt;br /&gt;
&lt;br /&gt;
==Binäre Suche==&lt;br /&gt;
&lt;br /&gt;
Wie wir weiter unten zeigen werden, gestattet es diese Suchmethode, die Gesamtdauer der Suche in großen Datensätzen beträchtlich zu verringern. Die Methode beruht auf dem [http://de.wikipedia.org/wiki/Divide_and_Conquer Divide and Conquer-Prinzip], wobei die Suche in jedem Schritt rekursiv auf eine Hälfte des Datensatzes eingeschränkt wird. Weitere Details zur Methode sind [http://de.wikipedia.org/wiki/Bin%C3%A4re_Suche hier] zu finden. &lt;br /&gt;
&lt;br /&gt;
Die Methode ist nur dann anwendbar beziehungsweise effektiv, wenn folgendes gilt:&lt;br /&gt;
&lt;br /&gt;
# Auf der Eigenschaft der Daten, die zur Suche verwendet wird, ist eine Ordnung im Sinne von &amp;lt; oder &amp;gt; definiert.&lt;br /&gt;
# Wir wollen uns auf Datensätze beschränken, die schon fertig aufgebaut sind, in die also keine neuen Elemente mehr eingefügt werden, wenn man mit dem Suchen beginnt. Ist dies nicht der Fall, so ist unter Umständen die Implementierung über einen [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum Binärbaum] (siehe auch weiter unten) geschickter. &lt;br /&gt;
&lt;br /&gt;
Der folgende Algorithmus zeigt eine beispielhafte Implementierung der Methode (wir setzen wieder &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt;):&lt;br /&gt;
&lt;br /&gt;
 a = [...,...]     # array&lt;br /&gt;
 a.sort()   # sortiere über Ordnung des Schlüssels&lt;br /&gt;
 foundIndex = binSearch(a, key, 0, len(a))  # (Array, Schlüssel, von wo bis wo suchen im Array)&lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq&amp;lt;/math&amp;gt;  foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def binSearch(a, key, start, end):  # start ist 1. Index, end ist letzter Index + 1&lt;br /&gt;
    size = end - start   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if size &amp;lt;= 0:   # Bereich leer?  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return -1   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    center = (start + end)/2   # Integer Division, Ergebnis wird abgerundet, wichtig für ganzzahlige Indizes &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if a[center] == key:  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return center  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    elif a[center] &amp;lt; key:  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return binSearch(a, key, center + 1, end)  # Rekursion&lt;br /&gt;
    else:&lt;br /&gt;
        return binSearch(a, key, start + 1, center)  # Rekursion&lt;br /&gt;
&lt;br /&gt;
Zur Berechnung der Komplexität dieses Algorithmus vernachlässigen wir zunächst den Aufwand, den die Sortierung weiter oben verursacht. Dieser Schritt mag oder mag nicht zulässig sein.&lt;br /&gt;
&lt;br /&gt;
Nach der Sequenzregel haben auch alle &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;.  Es bleibt die Komplexität der Rekursion zu berechnen. Die gesamte Komplexität des Algorithmus (jetzt als Funktion f bezeichnet) setzt sich zusammen aus den oben erwähnten &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen sowie der Rekursion und ist &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f(N) = \mathcal{O}(1) + f(N/2) = \mathcal{O}(1) + \mathcal{O}(1) + f(N/4) = ... = \underbrace{\mathcal{O}(1) + ... + \mathcal{O}(1) + \underbrace{f(0)}_{\mathcal{O}(1)\, \rightarrow \,\mathrm{size-Abfrage}}}_{n+1 \,\mathrm{Terme}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Falls jetzt gilt &amp;lt;math&amp;gt; N = 2^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \rightarrow f(N) = \mathcal{O}(1) \cdot \mathcal{O}(n+1) = \mathcal{O}(n) = \mathcal{O}(\lg N) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Für große Datenmengen ist die ''binäre Suche'' also weit effizienter als die ''lineare Suche''. Verdoppelt sich beispielsweise die zu durchsuchende Datenmenge, so verdoppelt sich der Aufwand für die ''sequentielle Suche'' - bei der ''binären Suche'' hingegen benötigt man lediglich eine zusätzliche Vergleichsoperation. &lt;br /&gt;
&lt;br /&gt;
Für kleine Daten (&amp;lt;math&amp;gt; N = 4,\, 5 &amp;lt;/math&amp;gt;) ist die ''sequentielle Suche'' jedoch schneller als die ''binäre Suche'', da hier die rekursiven Funktionsaufrufe teurer als das Mehr an Vergleichen sind. Ein anderer ungünstiger Fall ist gegeben, wenn nur sehr wenige Suchanfragen erfolgen (weniger als &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; viele). Dann wird der Aufwand durch das Sortieren des Arrays dominiert, ist also &amp;lt;math&amp;gt;\mathcal{O}(N \lg N) &amp;lt;/math&amp;gt;. Auch dann ist sequentielle Suche vorzuziehen.&lt;br /&gt;
&lt;br /&gt;
Eine relativ einfache Möglichkeit, die ''binäre Suche'' zu verbessern, ist die sogenannte ''Interpolationssuche''. Hierbei wird die neue Position für die Suche, also die Mitte des Arrays, durch eine Schätzung ersetzt, die angibt, wo sich der Schlüssel innerhalb des Arrays befinden könnte. Bei der Suche in einem Telefonbuch nach dem Namen Zebra würde man ja auch nicht in der Mitte anfangen. Näheres hierzu im Buch von ''Sedgewick''.&lt;br /&gt;
&lt;br /&gt;
Um sich den Algorithmus der ''binären Suche'' klar zu machen, ist es instruktiv, sich die folgende Tabelle genauer anzusehen, die die sukzessive Belegung der Variablen bei verschiedenen Anfragen beschreibt. Die Testfälle wurden nach dem Prinzip des ''domain partitioning'' gewählt. Das zugehörige Array hat die Einträge&lt;br /&gt;
&lt;br /&gt;
 a = [2, 3, 4, 5, 6]&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align:center&amp;quot; border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;5&amp;quot; cellspacing=&amp;quot;0&amp;quot; &lt;br /&gt;
! gesuchter key   !!  start      !! end  !! size !! center !! return &amp;lt;br/&amp;gt; (-1 oder index)  !! Kommentare  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 4     ||0            || 5    ||  5   || 2   ||  2         || gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 2     || 0           || 5    ||  5   || 2   ||            || linker Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||           &lt;br /&gt;
|-&lt;br /&gt;
|       ||  0          || 1    ||  1   || 0   ||  0         || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 1     ||0            || 5    ||  5   || 2   ||            || links außerhalb&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 1    ||  1   || 0   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 0    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 6     ||0            || 5    ||  5   || 2   ||            || rechter Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    || 2    || 4   || 4          || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 5     ||0            || 5    ||  5   || 2   ||            || typischer Fall&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||3            || 5    ||  2   || 4   ||            ||  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       || 3           || 4    ||  1   || 3   || 3          || gefunden&lt;br /&gt;
|- &lt;br /&gt;
| 7     ||0            || 5    ||  5   || 2   ||            || rechts außerhalb        &lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    ||  2   || 4   ||            ||&lt;br /&gt;
|-&lt;br /&gt;
|       ||5            || 5    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Suche in einem Binärbaum ==&lt;br /&gt;
&lt;br /&gt;
Eine kurze Einführung in Binärbäume findet man [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum hier].&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum.png|text-top|300x300px|Zur Illustration von Bäumen]]&lt;br /&gt;
&lt;br /&gt;
Bäume sind zweidimensional verkettete Strukturen. Sie gehören zu den fundamentalen Datenstrukturen in der Informatik. Da man in Bäumen nicht nur Daten speichern kann, sondern auch relevante Beziehungen der Daten untereinander, festgelegt über eine Ordnung auf der vergleichenden Dateneigenschaft (''Schlüssel''), eignen sich Bäume also insbesondere, um gesuchte Daten schnell wieder auffinden zu können.&lt;br /&gt;
&lt;br /&gt;
Ein ''Binärbaum'' wie oben skizziert besteht aus einer Menge von ''Knoten'', die untereinander durch ''Kanten'' verbunden sind. Jeder Knoten hat einen linken und einen rechten Unterbaum, der auch leer sein kann (in Python ließe sich dies mit ''None'' implementieren). Führt eine Kante von Knoten A zu Knoten B, so heißt A Vater von B und B Kind von A. Es gibt genau einen Knoten ohne Vater, den man ''Wurzel'' nennt. Knoten ohne Kinder heißen ''Blätter''.&lt;br /&gt;
&lt;br /&gt;
Ein ''Suchbaum'' hat zusätzlich die Eigenschaft, dass die Schlüssel jedes Knotens sortiert sind. Alle Schlüssel im linken Unterbaum sind kleiner, alle Schlüssel im rechten Unterbaum sind größer als ihr Vater. Wir wollen hierbei annehmen, dass jeder Schlüssel pro Datensatz nur einmal vorkommt, da sich sonst die &amp;gt;- oder &amp;lt;-Relation nicht mehr strikt erfüllen ließe.&lt;br /&gt;
&lt;br /&gt;
Um in einem Baum suchen zu können, wollen wir von zwei Annahmen ausgehen:&lt;br /&gt;
# Einfügen und Suchen im Baum wechseln sich ab. (Wenn das Suchen erst beginnt, nachdem alle Einfügungen erfolgt sind, wäre ein dynamisches Array mit binärer Suche wie oben wesentlich einfacher.)&lt;br /&gt;
# Der Schlüssel, der die Anordnung bestimmt, kennt eine [http://de.wikipedia.org/wiki/Ordnungsrelation Ordnung] (&amp;lt;-Relation oder &amp;gt;-Relation).&lt;br /&gt;
&lt;br /&gt;
Der folgende Python-Code zeigt beispielhaft, wie man in einem Suchbaum suchen könnte. Der Konstruktor für einen Knoten des Suchbaums ließe sich zum Beispiel so implementieren:&lt;br /&gt;
 &lt;br /&gt;
 class Node:&lt;br /&gt;
     def __init__(self, key):&lt;br /&gt;
         self.key = key&lt;br /&gt;
         self.left = self.right = None&lt;br /&gt;
 &lt;br /&gt;
 root = ...    # Wurzel des Suchbaums&lt;br /&gt;
 nodeFound = treeSearch(root, key)   # None, falls nichts gefunden&lt;br /&gt;
 &lt;br /&gt;
 def treeSearch(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return None&lt;br /&gt;
     elif node.key == key:&lt;br /&gt;
         return node&lt;br /&gt;
     elif key &amp;lt; node.key:&lt;br /&gt;
         return treeSearch(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         return treeSearch(node.right, key)&lt;br /&gt;
&lt;br /&gt;
Daraus resultiert der folgende Suchalgorithmus:&lt;br /&gt;
&lt;br /&gt;
 def treeSort(node,array):     # dynamisches Array als 2. Argument&lt;br /&gt;
     if node is None:     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
         return None:&lt;br /&gt;
     treeSort(node.left, array)     # rekursiv&lt;br /&gt;
     array.append(node.key)     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
     treeSort(node.right, array)     # rekursiv&lt;br /&gt;
&lt;br /&gt;
Komplexität:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 f(N)=\mathcal{O}(1)+f(N_\mathrm{left})+f(N_\mathrm{right})=\mathcal{O}(1)+\mathcal{O}(1)+f(N_\mathrm{leftleft})+f(N_\mathrm{leftright})+\mathcal{O}(1)+f(N_\mathrm{rightleft})&lt;br /&gt;
 +f(N_\mathrm{leftright})=N\ast\mathcal{O}(1)=\mathcal{O}(N)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sortier-Pseudocode:&lt;br /&gt;
&lt;br /&gt;
 Sortieren:&lt;br /&gt;
     (Array) a     # unsortiert&lt;br /&gt;
     (tree) t     # zunächst leer&lt;br /&gt;
 (dynamisches Array) r     # später sortiert&lt;br /&gt;
 for e in a:&lt;br /&gt;
     t = treeInsert(t, e)&lt;br /&gt;
 treeSort(t, r)&lt;br /&gt;
&lt;br /&gt;
== Insert ==&lt;br /&gt;
&lt;br /&gt;
Was passiert wenn der key (Schlüssel) schon vorhanden ist?&lt;br /&gt;
* error (exception)&lt;br /&gt;
* nichts einfügen&lt;br /&gt;
* nichts einfügen aber einen boolean return zurückgeben (einfügen=true, false)&lt;br /&gt;
* nochmals eingefügt (z.B. in der Node Klasse)&lt;br /&gt;
&lt;br /&gt;
Wobei die ersten 3 Punkte zur Mengensemantik gehören und der letzte eine Multimenge ist.&lt;br /&gt;
&lt;br /&gt;
Algorithmus im zweiten Fall (nichts einfügen):&lt;br /&gt;
&lt;br /&gt;
 def treeInsert(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return Node(key)     # Alternative Schreibweise: node = Node(key)&lt;br /&gt;
     if node.key == key:      # und dann:&lt;br /&gt;
         return node          # pass&lt;br /&gt;
     elif key &amp;lt; node.key:     # links im Baum&lt;br /&gt;
         node.left = treeInsert(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         node.right = treeInsert(node.right, key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Remove ==&lt;br /&gt;
Fälle:&lt;br /&gt;
# key (bzw. Knoten der key enthält) ist ein Blatt =&amp;gt; einfach löschen&lt;br /&gt;
# node hat &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; linken Unterbaum oder &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; rechten Unterbaum =&amp;gt; durch Unterbaum ersetzen&lt;br /&gt;
# node hat beide Unterbäume:&lt;br /&gt;
#* Suche Vorgänger: max k &amp;lt; key (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; keys); Vorgänger ist immer ein Fall 1 oder Fall 2&lt;br /&gt;
#*:=&amp;gt; ersetze node durch Vorgänger und entferne Vorgänger&lt;br /&gt;
&lt;br /&gt;
 def treePredecessor(node):     # wird nur bei Fall 3 aufgerufen&lt;br /&gt;
     node = node.left&lt;br /&gt;
     while node.right is not None:&lt;br /&gt;
         node = node.right&lt;br /&gt;
     return node&lt;br /&gt;
 &lt;br /&gt;
 def treeRemove(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return node&lt;br /&gt;
     if key &amp;lt; node.key:&lt;br /&gt;
         node.left = treeRemove(node.left, key)&lt;br /&gt;
     elif key &amp;gt; node.key:&lt;br /&gt;
         node.right = treeRemove(node.right, key)&lt;br /&gt;
     else:&lt;br /&gt;
         if node.left is None and node.right is None:     # Fall 1&lt;br /&gt;
             node = None&lt;br /&gt;
         elif node.left is None:     # Fall 2&lt;br /&gt;
             node = node.right       # +&lt;br /&gt;
         elif node.right is None:    # Fall 2&lt;br /&gt;
             node = node.left&lt;br /&gt;
         else:&lt;br /&gt;
             pred = treePredecessor(node)&lt;br /&gt;
             node.key = pred.key&lt;br /&gt;
             node.left = treeRemove(node.left, pred.key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Komplexitätsanalyse ==&lt;br /&gt;
&lt;br /&gt;
* Pfad (Zwischen node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;): &lt;br /&gt;
**Folge von Knoten (node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt;,...,node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt;), sodass:&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;ki&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;ki+1&amp;lt;/sub&amp;gt; haben eine gemeinsame Kante.&lt;br /&gt;
[[Image:Baum_Pfad.png]]&lt;br /&gt;
(Ein Baum ist ein Graph, indem es zwischen beliebigen Knoten stets genau einen Pfad gibt.)&lt;br /&gt;
&lt;br /&gt;
* Länge eines Pfades: Anzahl der Kanten = Anzahl der Knoten - 1&lt;br /&gt;
* Tiefe eines Knotens: Pfadlänge vom Knoten zur Wurzel des Baumes.&lt;br /&gt;
* Tiefe des Baumes: maximale Tiefe eines Knotens&lt;br /&gt;
* Balance eines Baumes: maximale Tiefe(k) - minimale Tiefe(k) (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; Blätter)&lt;br /&gt;
&lt;br /&gt;
== Ungünstigster Fall von treeSearch ==&lt;br /&gt;
&lt;br /&gt;
Komplexität von treeSearch = Länge des Pfades zum Knoten wo &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; gefunden wird, oder erkannt wird, dass &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; nicht im Baum ist.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; wird nicht gefunden, aber für diese Entscheidung muss der Längste Pfad vollständig durchlaufen werden.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(T)&amp;lt;/math&amp;gt; wo T = Tiefe des Baumes&lt;br /&gt;
*: [1,2,3,4,5]:&amp;lt;br /&amp;gt; [[Image:Balance.png]]&lt;br /&gt;
*: Tiefe T = 4, Balance = 4&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; wo N = Anzahl der Elemente.&lt;br /&gt;
&lt;br /&gt;
== Aufgabe ==&lt;br /&gt;
&lt;br /&gt;
Minimiere Balance (erzeuge balancierten Baum):&lt;br /&gt;
# Einfügen in geschickter Reihenfolge (siehe Übungsaufgabe)&lt;br /&gt;
# Selbstbalancierter Baum:&lt;br /&gt;
#* Überprüfen der Balance nach jedem Einfügen&lt;br /&gt;
#* Umstrukturieren des Baumes, falls Balance &amp;gt; 1 (Suchbaum-Bedingung muss erhalten bleiben)&lt;br /&gt;
#* AVL-Bäume (älteste Variante)&lt;br /&gt;
#* Rot-Schwarz-Bäume (verbreiteste Variante)&lt;br /&gt;
#* Treaps (flexibelste Variante, siehe Übung)&lt;br /&gt;
#* Splay trees&lt;br /&gt;
#* Andersson Trees (einfachste Variante)&lt;br /&gt;
(#* Skip Lists (schnellste Variante, aber kein Binärbaum))&lt;br /&gt;
&lt;br /&gt;
== Umstrukturieren, so dass Suchbaumbedingung erhalten bleibt: ==&lt;br /&gt;
&lt;br /&gt;
Rotation: elementare Umstrukturierungen&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum_Rotation.png]]&lt;br /&gt;
&lt;br /&gt;
 def rotateRight(node):&lt;br /&gt;
     newRoot = node.left&lt;br /&gt;
     node.left = newRoot.right&lt;br /&gt;
     newRoot.right = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
 def rotateLeft(node):&lt;br /&gt;
     newRoot = node.right&lt;br /&gt;
     node.right = newRoot.left&lt;br /&gt;
     newRoot.left = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
== Balance eines Suchbaumes ==&lt;br /&gt;
&lt;br /&gt;
===Balance eines Baumes zu  definieren:===&lt;br /&gt;
[[Image:Abbildung1.jpg|250px|right]]&lt;br /&gt;
*betrachte &amp;lt;tt&amp;gt;None&amp;lt;/tt&amp;gt; als zusätzlichen Knoten : '''sentinel'''   ([[englische Sprache|engl.]] für ''Wächter'')&lt;br /&gt;
*oder defeniere speziellen sentinel-Knoten&lt;br /&gt;
*RS - Pfades : von &amp;lt;tt&amp;gt;root&amp;lt;/tt&amp;gt; &amp;amp;rarr; ''sentinel''&lt;br /&gt;
*Länge eines Pfades ''P''      '''''|P|'''''&lt;br /&gt;
* &amp;lt;u&amp;gt;die Balance eines Baumes zu defenieren:&amp;lt;/u&amp;gt;&lt;br /&gt;
: '''''B = max P '''''&amp;lt;math&amp;gt; \in &amp;lt;/math&amp;gt; '''''{RS}    |P| - min |P|'''''&lt;br /&gt;
:'''''{RS}'''''  Menge aller RS-Pfade&lt;br /&gt;
;vollständiger Baum:  Balance &amp;lt;math&amp;gt;~=0&amp;lt;/math&amp;gt;&lt;br /&gt;
:Alle Knoten(außer Blättern) haben 2 Kinder.&lt;br /&gt;
;perfect balancierter Baum:  Balance  &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 1&lt;br /&gt;
'''alternative Defenition für perfect balancierte Bäume'''&lt;br /&gt;
:Für jeden Teilbaum gilt es: rechtes und linkes Kind (für jeden Knoten) sind auch wieder perfect balancierte Bäume und ihre Höhe unterscheidet sich höchstens um '''1'''.&lt;br /&gt;
&lt;br /&gt;
===Größe===&lt;br /&gt;
[[Image:baum-v.png|700px|right]]&lt;br /&gt;
====Größe des vollständigen Baumes====&lt;br /&gt;
Ebene K hat 2 &amp;lt;sup&amp;gt;k&amp;lt;/sup&amp;gt; Knoten.&lt;br /&gt;
Falls Tiefe = d , dann N = 2&amp;lt;sup&amp;gt;0&amp;lt;/sup&amp;gt; + 2&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;.....+ 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; = 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1&lt;br /&gt;
&lt;br /&gt;
====Größe des perfect balancierten Baumes====&lt;br /&gt;
Die Tiefe d, kann nicht besser sein als vollständiger Baum&lt;br /&gt;
:'''=&amp;gt;'''N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2 &amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
;schlechteste perfect balancierter Baum : ist ein vollständiger der Tiefe  d - 1 + 1, N &amp;lt;math&amp;gt; \ge&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:'''=&amp;gt;'''  2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1)   &amp;lt;math&amp;gt;~&amp;lt;&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt;)&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
d &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt;  log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; (d + 1)&lt;br /&gt;
&lt;br /&gt;
*Ergibt die Komplexität der Suche im schlechtesten Fall: Anzahl der Vergleiche pro Knoten( = 2 bzw. = 3)&amp;lt;math&amp;gt;\ast&amp;lt;/math&amp;gt;Anzahl der Knoten&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow ~f(N)\le 2d \le 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
*'''perfekt balancierter Baum '''=&amp;gt; AVL-Baum &lt;br /&gt;
*'''balancierter Baum''':&lt;br /&gt;
::&amp;lt;math&amp;gt;\forall  N:d(N)\le c \ast d (N)&amp;lt;/math&amp;gt; und  &amp;lt;math&amp;gt;1 \le c &amp;lt; \mathcal {1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:: d ist die Tiefe von perfekt balancierten Baum&lt;br /&gt;
&lt;br /&gt;
:''Komplexität der Suche'':&amp;lt;math&amp;gt; ~f(N)\le  c\ast 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
:algorithmisch einfacher als perfekt balancierter Baum, aber fast genauso schnell&lt;br /&gt;
&lt;br /&gt;
====Selbst-balancierter Baum====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  def insertTree(node,key):&lt;br /&gt;
    if node is sentinel:       #(None = sentinel)&lt;br /&gt;
        return Node(key)       &lt;br /&gt;
    if node.key == key:&lt;br /&gt;
        return Node&lt;br /&gt;
    if key &amp;lt; node.key:&lt;br /&gt;
        Node = insertTree(node.left, key)&lt;br /&gt;
    else:&lt;br /&gt;
        node.right = insertTree(node.right, key)&lt;br /&gt;
   #optimiere Balance:&lt;br /&gt;
        return node&lt;br /&gt;
&lt;br /&gt;
===Anderson-Bäume===&lt;br /&gt;
&lt;br /&gt;
  class Node:&lt;br /&gt;
    def__init__(self, key):&lt;br /&gt;
        self.key = key&lt;br /&gt;
        self.left = selft.right = None&lt;br /&gt;
        #einfügen:&lt;br /&gt;
        self.level = 1&lt;br /&gt;
&lt;br /&gt;
:''level'' : kodiert Abstand von sentinel&lt;br /&gt;
====Regeln====&lt;br /&gt;
Es gibt vertikale Kanten(parent.level == child.level + 1 ) und horizontale Kanten(parent.level == child.level)&lt;br /&gt;
:*verfeinerter Regel :  ''level kodiert reduzierten Abstand von Setinel''  (d.b. horizontale Kanten werden nicht gezählt)&lt;br /&gt;
::(die nächste zwei Regeln sichern die Balance):&lt;br /&gt;
::*alle RS-Pfade haben die gleiche reduzierte Länge oder ''root'' hat bei allen Pfaden das gleiche Level&lt;br /&gt;
::*kein Pfad hat 2 aufeinanderer folgende horizontale Kanten&lt;br /&gt;
:*nur Kanten zum rechten Kind dürfen horizontal sein&lt;br /&gt;
:*die reduzierte Höhe jedes Blatts ist '''hr=1'''&lt;br /&gt;
&lt;br /&gt;
====Beweis====&lt;br /&gt;
Vereinfachung des Algorithmus&lt;br /&gt;
:'''Satz''':ein Anderson-Baum ist balanciert.&lt;br /&gt;
*1. Sei '''hr'''- die reduzierte Höhe&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; jeder Teilbaum enthält mindestens &amp;lt;math&amp;gt;N\ge 2^{hr} -1 &amp;lt;/math&amp;gt;  Knoten&lt;br /&gt;
&lt;br /&gt;
::a). Blätter : reduzierte Höhe 1 =&amp;gt; &amp;lt;math&amp;gt; N\ge 2^{1} - 1 = 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
::b). inneren Knoten: jeder Unterbaum hat mindestens reduzierte Höhe &amp;lt;math&amp;gt; ~hr - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt;  jeder Unterbaum hat mindestens &amp;lt;math&amp;gt; ~2^{hr} -1 &amp;lt;/math&amp;gt; Knoten&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt;N \ge 2 (2^{hr-1} -1)+1 = 2^w - 2 + 1 = 2^{hr} -1&amp;lt;/math&amp;gt;&lt;br /&gt;
::::alle RS-Pfade heben gleiche Länge&lt;br /&gt;
[[Image:Anderson.png|700px|right]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
*2. Kein Pfad hat 2 aufeinanderfolgende horizontal Kanten&lt;br /&gt;
:::&amp;lt;math&amp;gt;\Rightarrow  d \le 2 hr + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
*3. zusammen:&lt;br /&gt;
:&amp;lt;math&amp;gt;N \ge 2^{\frac {d}{2}} - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;log_{2}{ N}+1\ge log 2^{\frac {d}{2}} - 1 \ge \frac {d}{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; ~d &amp;lt; 2 log_{2}{(N + 1)}&amp;lt;/math&amp;gt;     - balancierter Baum&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
:'''Suchzeit''' : &amp;lt;math&amp;gt; ~ f(N) = \mathcal{O}(log{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Wie erreicht man die Balance===&lt;br /&gt;
====Rotation====&lt;br /&gt;
[[Image:Abbildung4.jpg|text-top|350px|Illustration von Rotation]]&lt;br /&gt;
&lt;br /&gt;
   def rotateRight (node):&lt;br /&gt;
      root = node.left&lt;br /&gt;
      node.left = node.right&lt;br /&gt;
      root.right = node&lt;br /&gt;
      return root&lt;br /&gt;
   def rotateLeft(node):&lt;br /&gt;
      root = node.right&lt;br /&gt;
      node.right = node.left&lt;br /&gt;
      root.left = node&lt;br /&gt;
      return root&lt;br /&gt;
&lt;br /&gt;
====Optimierung der Balance====&lt;br /&gt;
   &lt;br /&gt;
&lt;br /&gt;
       if node.left is not sentinel and node.level==node.left.level:&lt;br /&gt;
            node &amp;gt; rotateRight(node)&lt;br /&gt;
       if node.right is not sentinel and node.right.right is  not sentinel and node.level==rotate.right.right.level:&lt;br /&gt;
            node = rotateLeft(node)&lt;br /&gt;
            node.level += 1&lt;br /&gt;
[[Image:rotate.jpg|centre]]&lt;/div&gt;</summary>
		<author><name>Nata</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=File:Rotate.jpg&amp;diff=1504</id>
		<title>File:Rotate.jpg</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=File:Rotate.jpg&amp;diff=1504"/>
				<updated>2008-05-29T00:30:41Z</updated>
		
		<summary type="html">&lt;p&gt;Nata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Nata</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1503</id>
		<title>Suchen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1503"/>
				<updated>2008-05-29T00:29:08Z</updated>
		
		<summary type="html">&lt;p&gt;Nata: /* Rotation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Es wäre super wenn jemand ganz kurz schreiben könnte, was am Donnerstag zu den Funktionen treeInsert/Remove/HasKey gemacht wurde, was man ja für den aktuellen Übungszettel braucht. Danke :-)&amp;lt;br /&amp;gt;&lt;br /&gt;
/edit: an eine Funktion &amp;quot;HasKey&amp;quot; kann ich mich aus der Vorlesung nicht erinnern. Wenn die jemand hat, bitte eintragen. - Protokollant&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--Hallo, ich kenne leider deinen Namen nicht, ich habe den Wiki-Eintrag von letztem Donnerstag gemacht.Da das Thema 'Dynamisches Array' noch Thema von letzter Woche war, hab ich die Wiederholung der amortisierten Kosten von diesem Mittwoch noch bei Effizienz eingetragen, du musst es also nicht noch mal in deinem Wiki eintragen. Du kannst dir aber gerne mal anschauen was ich geschrieben habe, vielleicht fallen dir noch ein paar Sachen ein, die man hätte besser machen können. lg, Franziska.--&amp;gt;&lt;br /&gt;
Das Suchen ist eine grundlegende Operation in der Informatik. Viele Probleme in der Informatik können auf Suchaufgaben zurückgeführt werden.&lt;br /&gt;
&lt;br /&gt;
Gemeint ist mit Suchen das Wiederauffinden einer oder mehrerer Datensätze aus einer Menge von früher gespeicherten Datensätzen. Ein paar einleitende Worte zum Suchproblem findet man [http://de.wikipedia.org/wiki/Suche hier].&lt;br /&gt;
&lt;br /&gt;
== Überblick verschiedener Suchmethoden ==&lt;br /&gt;
&lt;br /&gt;
Um sich der Vielseitigkeit von Suchproblemen bewusst zu werden, ist es sinnvoll, sich einen Überblick über verschiedene Suchmethoden zu verschaffen. &lt;br /&gt;
&lt;br /&gt;
Hier sei auch auf einen bereits existierenden Wikipedia-Artikel zu [http://de.wikipedia.org/wiki/Suchverfahren Suchverfahren] verwiesen.&lt;br /&gt;
&lt;br /&gt;
Allen gemeinsam ist die grundlegende Aufgabe, ein Datenelement mit bestimmten Eigenschaften aus einer großen Menge von Datenelementen zu selektieren.&lt;br /&gt;
Dies kann, natürlich ohne jeden Anspruch auf Vollständigkeit, nach einer der jetzt diskutierten Methoden geschehen:&lt;br /&gt;
&lt;br /&gt;
* '''Schlüsselsuche''': meint das Suchen von Elementen mit bestimmtem Schlüssel; ein klassisches Beispiel wäre das Suchen in einem Wörterbuch,  die Schlüssel entsprechen hier den Wörtern, die Datensätze wären die zu den Wörtern gehörigen Eintragungen.&lt;br /&gt;
&lt;br /&gt;
* '''Bereichssuche''': Im Allgemeinen meint die Bereichssuche in n-Dimensionen die Selektion von Elementen mit Eigenschaften aus einem bestimmten n-dimensionalen Volumen. Im eindimensionalen Fall will man alle Elemente finden, deren Eigenschaft(en) in einem bestimmten Intervall liegen. Die Verallgemeinerung auf n-Dimensionen ist offensichtlich. Ein Beispiel für die Bereichssuche in einer 3D-Kugel wäre ein Handy mit Geolokalisierung, welches alle Restaurants in einem Umkreis von 500m findet. Lineare Ungleichungen werden graphisch durch [http://de.wikipedia.org/wiki/Hyperebene Hyperebenen] repräsentiert. In 2D sind diese Hyperebenen Geraden. Die Ungleichungen können dann den Lösungsraum in irgendeiner Form begrenzen.&lt;br /&gt;
&lt;br /&gt;
* '''Ähnlichkeitssuche''': Finde Elemente, die gegebenen Eigenschaften möglichst ähnlich sind. Ein prominentes Beispiel ist Google (=Ähnlichkeit zwischen Suchbegriffen und Dokumenten) oder das Suchen des nächstengelegenen Restaurants (Ähnlichkeit zwischen eigener Position und Position des Restaurants). Ein wichtigster Spezialfall ist die ''nächste-nachbar Suche''.&lt;br /&gt;
&lt;br /&gt;
* '''Graphensuche''': Hier wäre beispielsweise das Problem optimaler Wege zu nennen (Navigationssuche). Dieser Punkt wird später im Verlauf der Vorlesung noch einmal aufgegriffen werden.&lt;br /&gt;
&lt;br /&gt;
Im jetzt Folgenden wird nur noch die ''Schlüsselsuche'' betrachtet werden.&lt;br /&gt;
&lt;br /&gt;
==Sequentielle Suche==&lt;br /&gt;
&lt;br /&gt;
Die ''sequentielle'' oder ''lineare'' Suche ist die 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 = sequentialSearch(a, key) &lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq &amp;lt;/math&amp;gt; foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def sequentialSearch(a, key):&lt;br /&gt;
    for i in range(len(a)):&lt;br /&gt;
        if a[i] == key:  # bzw. allgemeiner a[i].key == key &lt;br /&gt;
            return i&lt;br /&gt;
    return -1&lt;br /&gt;
&lt;br /&gt;
Wir wollen jetzt die Komplexität dieses Algorithmus bestimmen, wobei die Problemgröße durch &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt; gegeben ist. &lt;br /&gt;
&lt;br /&gt;
Dabei nimmt man an, dass der innerste Vergleich (a[i] == key) jeweils &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt; ist (diese Annahme könnte verletzt sein, wenn der Vergleichsoperator überladen ist und dadurch eine höhere Komplexität hat). Dieser Vergleich wird in der for-Schleife jeweils N-mal durchgeführt (&amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt;), so dass man nach der Verschachtelungsregel eine gesamte Komplexität von &amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt; erhält.&lt;br /&gt;
&lt;br /&gt;
Der Name ''lineare'' Suche rührt von diesem linearen Anwachsen der Komplexität mit der Arraygröße her.&lt;br /&gt;
&lt;br /&gt;
==Binäre Suche==&lt;br /&gt;
&lt;br /&gt;
Wie wir weiter unten zeigen werden, gestattet es diese Suchmethode, die Gesamtdauer der Suche in großen Datensätzen beträchtlich zu verringern. Die Methode beruht auf dem [http://de.wikipedia.org/wiki/Divide_and_Conquer Divide and Conquer-Prinzip], wobei die Suche in jedem Schritt rekursiv auf eine Hälfte des Datensatzes eingeschränkt wird. Weitere Details zur Methode sind [http://de.wikipedia.org/wiki/Bin%C3%A4re_Suche hier] zu finden. &lt;br /&gt;
&lt;br /&gt;
Die Methode ist nur dann anwendbar beziehungsweise effektiv, wenn folgendes gilt:&lt;br /&gt;
&lt;br /&gt;
# Auf der Eigenschaft der Daten, die zur Suche verwendet wird, ist eine Ordnung im Sinne von &amp;lt; oder &amp;gt; definiert.&lt;br /&gt;
# Wir wollen uns auf Datensätze beschränken, die schon fertig aufgebaut sind, in die also keine neuen Elemente mehr eingefügt werden, wenn man mit dem Suchen beginnt. Ist dies nicht der Fall, so ist unter Umständen die Implementierung über einen [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum Binärbaum] (siehe auch weiter unten) geschickter. &lt;br /&gt;
&lt;br /&gt;
Der folgende Algorithmus zeigt eine beispielhafte Implementierung der Methode (wir setzen wieder &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt;):&lt;br /&gt;
&lt;br /&gt;
 a = [...,...]     # array&lt;br /&gt;
 a.sort()   # sortiere über Ordnung des Schlüssels&lt;br /&gt;
 foundIndex = binSearch(a, key, 0, len(a))  # (Array, Schlüssel, von wo bis wo suchen im Array)&lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq&amp;lt;/math&amp;gt;  foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def binSearch(a, key, start, end):  # start ist 1. Index, end ist letzter Index + 1&lt;br /&gt;
    size = end - start   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if size &amp;lt;= 0:   # Bereich leer?  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return -1   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    center = (start + end)/2   # Integer Division, Ergebnis wird abgerundet, wichtig für ganzzahlige Indizes &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if a[center] == key:  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return center  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    elif a[center] &amp;lt; key:  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return binSearch(a, key, center + 1, end)  # Rekursion&lt;br /&gt;
    else:&lt;br /&gt;
        return binSearch(a, key, start + 1, center)  # Rekursion&lt;br /&gt;
&lt;br /&gt;
Zur Berechnung der Komplexität dieses Algorithmus vernachlässigen wir zunächst den Aufwand, den die Sortierung weiter oben verursacht. Dieser Schritt mag oder mag nicht zulässig sein.&lt;br /&gt;
&lt;br /&gt;
Nach der Sequenzregel haben auch alle &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;.  Es bleibt die Komplexität der Rekursion zu berechnen. Die gesamte Komplexität des Algorithmus (jetzt als Funktion f bezeichnet) setzt sich zusammen aus den oben erwähnten &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen sowie der Rekursion und ist &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f(N) = \mathcal{O}(1) + f(N/2) = \mathcal{O}(1) + \mathcal{O}(1) + f(N/4) = ... = \underbrace{\mathcal{O}(1) + ... + \mathcal{O}(1) + \underbrace{f(0)}_{\mathcal{O}(1)\, \rightarrow \,\mathrm{size-Abfrage}}}_{n+1 \,\mathrm{Terme}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Falls jetzt gilt &amp;lt;math&amp;gt; N = 2^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \rightarrow f(N) = \mathcal{O}(1) \cdot \mathcal{O}(n+1) = \mathcal{O}(n) = \mathcal{O}(\lg N) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Für große Datenmengen ist die ''binäre Suche'' also weit effizienter als die ''lineare Suche''. Verdoppelt sich beispielsweise die zu durchsuchende Datenmenge, so verdoppelt sich der Aufwand für die ''sequentielle Suche'' - bei der ''binären Suche'' hingegen benötigt man lediglich eine zusätzliche Vergleichsoperation. &lt;br /&gt;
&lt;br /&gt;
Für kleine Daten (&amp;lt;math&amp;gt; N = 4,\, 5 &amp;lt;/math&amp;gt;) ist die ''sequentielle Suche'' jedoch schneller als die ''binäre Suche'', da hier die rekursiven Funktionsaufrufe teurer als das Mehr an Vergleichen sind. Ein anderer ungünstiger Fall ist gegeben, wenn nur sehr wenige Suchanfragen erfolgen (weniger als &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; viele). Dann wird der Aufwand durch das Sortieren des Arrays dominiert, ist also &amp;lt;math&amp;gt;\mathcal{O}(N \lg N) &amp;lt;/math&amp;gt;. Auch dann ist sequentielle Suche vorzuziehen.&lt;br /&gt;
&lt;br /&gt;
Eine relativ einfache Möglichkeit, die ''binäre Suche'' zu verbessern, ist die sogenannte ''Interpolationssuche''. Hierbei wird die neue Position für die Suche, also die Mitte des Arrays, durch eine Schätzung ersetzt, die angibt, wo sich der Schlüssel innerhalb des Arrays befinden könnte. Bei der Suche in einem Telefonbuch nach dem Namen Zebra würde man ja auch nicht in der Mitte anfangen. Näheres hierzu im Buch von ''Sedgewick''.&lt;br /&gt;
&lt;br /&gt;
Um sich den Algorithmus der ''binären Suche'' klar zu machen, ist es instruktiv, sich die folgende Tabelle genauer anzusehen, die die sukzessive Belegung der Variablen bei verschiedenen Anfragen beschreibt. Die Testfälle wurden nach dem Prinzip des ''domain partitioning'' gewählt. Das zugehörige Array hat die Einträge&lt;br /&gt;
&lt;br /&gt;
 a = [2, 3, 4, 5, 6]&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align:center&amp;quot; border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;5&amp;quot; cellspacing=&amp;quot;0&amp;quot; &lt;br /&gt;
! gesuchter key   !!  start      !! end  !! size !! center !! return &amp;lt;br/&amp;gt; (-1 oder index)  !! Kommentare  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 4     ||0            || 5    ||  5   || 2   ||  2         || gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 2     || 0           || 5    ||  5   || 2   ||            || linker Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||           &lt;br /&gt;
|-&lt;br /&gt;
|       ||  0          || 1    ||  1   || 0   ||  0         || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 1     ||0            || 5    ||  5   || 2   ||            || links außerhalb&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 1    ||  1   || 0   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 0    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 6     ||0            || 5    ||  5   || 2   ||            || rechter Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    || 2    || 4   || 4          || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 5     ||0            || 5    ||  5   || 2   ||            || typischer Fall&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||3            || 5    ||  2   || 4   ||            ||  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       || 3           || 4    ||  1   || 3   || 3          || gefunden&lt;br /&gt;
|- &lt;br /&gt;
| 7     ||0            || 5    ||  5   || 2   ||            || rechts außerhalb        &lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    ||  2   || 4   ||            ||&lt;br /&gt;
|-&lt;br /&gt;
|       ||5            || 5    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Suche in einem Binärbaum ==&lt;br /&gt;
&lt;br /&gt;
Eine kurze Einführung in Binärbäume findet man [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum hier].&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum.png|text-top|300x300px|Zur Illustration von Bäumen]]&lt;br /&gt;
&lt;br /&gt;
Bäume sind zweidimensional verkettete Strukturen. Sie gehören zu den fundamentalen Datenstrukturen in der Informatik. Da man in Bäumen nicht nur Daten speichern kann, sondern auch relevante Beziehungen der Daten untereinander, festgelegt über eine Ordnung auf der vergleichenden Dateneigenschaft (''Schlüssel''), eignen sich Bäume also insbesondere, um gesuchte Daten schnell wieder auffinden zu können.&lt;br /&gt;
&lt;br /&gt;
Ein ''Binärbaum'' wie oben skizziert besteht aus einer Menge von ''Knoten'', die untereinander durch ''Kanten'' verbunden sind. Jeder Knoten hat einen linken und einen rechten Unterbaum, der auch leer sein kann (in Python ließe sich dies mit ''None'' implementieren). Führt eine Kante von Knoten A zu Knoten B, so heißt A Vater von B und B Kind von A. Es gibt genau einen Knoten ohne Vater, den man ''Wurzel'' nennt. Knoten ohne Kinder heißen ''Blätter''.&lt;br /&gt;
&lt;br /&gt;
Ein ''Suchbaum'' hat zusätzlich die Eigenschaft, dass die Schlüssel jedes Knotens sortiert sind. Alle Schlüssel im linken Unterbaum sind kleiner, alle Schlüssel im rechten Unterbaum sind größer als ihr Vater. Wir wollen hierbei annehmen, dass jeder Schlüssel pro Datensatz nur einmal vorkommt, da sich sonst die &amp;gt;- oder &amp;lt;-Relation nicht mehr strikt erfüllen ließe.&lt;br /&gt;
&lt;br /&gt;
Um in einem Baum suchen zu können, wollen wir von zwei Annahmen ausgehen:&lt;br /&gt;
# Einfügen und Suchen im Baum wechseln sich ab. (Wenn das Suchen erst beginnt, nachdem alle Einfügungen erfolgt sind, wäre ein dynamisches Array mit binärer Suche wie oben wesentlich einfacher.)&lt;br /&gt;
# Der Schlüssel, der die Anordnung bestimmt, kennt eine [http://de.wikipedia.org/wiki/Ordnungsrelation Ordnung] (&amp;lt;-Relation oder &amp;gt;-Relation).&lt;br /&gt;
&lt;br /&gt;
Der folgende Python-Code zeigt beispielhaft, wie man in einem Suchbaum suchen könnte. Der Konstruktor für einen Knoten des Suchbaums ließe sich zum Beispiel so implementieren:&lt;br /&gt;
 &lt;br /&gt;
 class Node:&lt;br /&gt;
     def __init__(self, key):&lt;br /&gt;
         self.key = key&lt;br /&gt;
         self.left = self.right = None&lt;br /&gt;
 &lt;br /&gt;
 root = ...    # Wurzel des Suchbaums&lt;br /&gt;
 nodeFound = treeSearch(root, key)   # None, falls nichts gefunden&lt;br /&gt;
 &lt;br /&gt;
 def treeSearch(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return None&lt;br /&gt;
     elif node.key == key:&lt;br /&gt;
         return node&lt;br /&gt;
     elif key &amp;lt; node.key:&lt;br /&gt;
         return treeSearch(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         return treeSearch(node.right, key)&lt;br /&gt;
&lt;br /&gt;
Daraus resultiert der folgende Suchalgorithmus:&lt;br /&gt;
&lt;br /&gt;
 def treeSort(node,array):     # dynamisches Array als 2. Argument&lt;br /&gt;
     if node is None:     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
         return None:&lt;br /&gt;
     treeSort(node.left, array)     # rekursiv&lt;br /&gt;
     array.append(node.key)     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
     treeSort(node.right, array)     # rekursiv&lt;br /&gt;
&lt;br /&gt;
Komplexität:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 f(N)=\mathcal{O}(1)+f(N_\mathrm{left})+f(N_\mathrm{right})=\mathcal{O}(1)+\mathcal{O}(1)+f(N_\mathrm{leftleft})+f(N_\mathrm{leftright})+\mathcal{O}(1)+f(N_\mathrm{rightleft})&lt;br /&gt;
 +f(N_\mathrm{leftright})=N\ast\mathcal{O}(1)=\mathcal{O}(N)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sortier-Pseudocode:&lt;br /&gt;
&lt;br /&gt;
 Sortieren:&lt;br /&gt;
     (Array) a     # unsortiert&lt;br /&gt;
     (tree) t     # zunächst leer&lt;br /&gt;
 (dynamisches Array) r     # später sortiert&lt;br /&gt;
 for e in a:&lt;br /&gt;
     t = treeInsert(t, e)&lt;br /&gt;
 treeSort(t, r)&lt;br /&gt;
&lt;br /&gt;
== Insert ==&lt;br /&gt;
&lt;br /&gt;
Was passiert wenn der key (Schlüssel) schon vorhanden ist?&lt;br /&gt;
* error (exception)&lt;br /&gt;
* nichts einfügen&lt;br /&gt;
* nichts einfügen aber einen boolean return zurückgeben (einfügen=true, false)&lt;br /&gt;
* nochmals eingefügt (z.B. in der Node Klasse)&lt;br /&gt;
&lt;br /&gt;
Wobei die ersten 3 Punkte zur Mengensemantik gehören und der letzte eine Multimenge ist.&lt;br /&gt;
&lt;br /&gt;
Algorithmus im zweiten Fall (nichts einfügen):&lt;br /&gt;
&lt;br /&gt;
 def treeInsert(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return Node(key)     # Alternative Schreibweise: node = Node(key)&lt;br /&gt;
     if node.key == key:      # und dann:&lt;br /&gt;
         return node          # pass&lt;br /&gt;
     elif key &amp;lt; node.key:     # links im Baum&lt;br /&gt;
         node.left = treeInsert(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         node.right = treeInsert(node.right, key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Remove ==&lt;br /&gt;
Fälle:&lt;br /&gt;
# key (bzw. Knoten der key enthält) ist ein Blatt =&amp;gt; einfach löschen&lt;br /&gt;
# node hat &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; linken Unterbaum oder &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; rechten Unterbaum =&amp;gt; durch Unterbaum ersetzen&lt;br /&gt;
# node hat beide Unterbäume:&lt;br /&gt;
#* Suche Vorgänger: max k &amp;lt; key (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; keys); Vorgänger ist immer ein Fall 1 oder Fall 2&lt;br /&gt;
#*:=&amp;gt; ersetze node durch Vorgänger und entferne Vorgänger&lt;br /&gt;
&lt;br /&gt;
 def treePredecessor(node):     # wird nur bei Fall 3 aufgerufen&lt;br /&gt;
     node = node.left&lt;br /&gt;
     while node.right is not None:&lt;br /&gt;
         node = node.right&lt;br /&gt;
     return node&lt;br /&gt;
 &lt;br /&gt;
 def treeRemove(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return node&lt;br /&gt;
     if key &amp;lt; node.key:&lt;br /&gt;
         node.left = treeRemove(node.left, key)&lt;br /&gt;
     elif key &amp;gt; node.key:&lt;br /&gt;
         node.right = treeRemove(node.right, key)&lt;br /&gt;
     else:&lt;br /&gt;
         if node.left is None and node.right is None:     # Fall 1&lt;br /&gt;
             node = None&lt;br /&gt;
         elif node.left is None:     # Fall 2&lt;br /&gt;
             node = node.right       # +&lt;br /&gt;
         elif node.right is None:    # Fall 2&lt;br /&gt;
             node = node.left&lt;br /&gt;
         else:&lt;br /&gt;
             pred = treePredecessor(node)&lt;br /&gt;
             node.key = pred.key&lt;br /&gt;
             node.left = treeRemove(node.left, pred.key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Komplexitätsanalyse ==&lt;br /&gt;
&lt;br /&gt;
* Pfad (Zwischen node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;): &lt;br /&gt;
**Folge von Knoten (node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt;,...,node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt;), sodass:&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;ki&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;ki+1&amp;lt;/sub&amp;gt; haben eine gemeinsame Kante.&lt;br /&gt;
[[Image:Baum_Pfad.png]]&lt;br /&gt;
(Ein Baum ist ein Graph, indem es zwischen beliebigen Knoten stets genau einen Pfad gibt.)&lt;br /&gt;
&lt;br /&gt;
* Länge eines Pfades: Anzahl der Kanten = Anzahl der Knoten - 1&lt;br /&gt;
* Tiefe eines Knotens: Pfadlänge vom Knoten zur Wurzel des Baumes.&lt;br /&gt;
* Tiefe des Baumes: maximale Tiefe eines Knotens&lt;br /&gt;
* Balance eines Baumes: maximale Tiefe(k) - minimale Tiefe(k) (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; Blätter)&lt;br /&gt;
&lt;br /&gt;
== Ungünstigster Fall von treeSearch ==&lt;br /&gt;
&lt;br /&gt;
Komplexität von treeSearch = Länge des Pfades zum Knoten wo &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; gefunden wird, oder erkannt wird, dass &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; nicht im Baum ist.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; wird nicht gefunden, aber für diese Entscheidung muss der Längste Pfad vollständig durchlaufen werden.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(T)&amp;lt;/math&amp;gt; wo T = Tiefe des Baumes&lt;br /&gt;
*: [1,2,3,4,5]:&amp;lt;br /&amp;gt; [[Image:Balance.png]]&lt;br /&gt;
*: Tiefe T = 4, Balance = 4&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; wo N = Anzahl der Elemente.&lt;br /&gt;
&lt;br /&gt;
== Aufgabe ==&lt;br /&gt;
&lt;br /&gt;
Minimiere Balance (erzeuge balancierten Baum):&lt;br /&gt;
# Einfügen in geschickter Reihenfolge (siehe Übungsaufgabe)&lt;br /&gt;
# Selbstbalancierter Baum:&lt;br /&gt;
#* Überprüfen der Balance nach jedem Einfügen&lt;br /&gt;
#* Umstrukturieren des Baumes, falls Balance &amp;gt; 1 (Suchbaum-Bedingung muss erhalten bleiben)&lt;br /&gt;
#* AVL-Bäume (älteste Variante)&lt;br /&gt;
#* Rot-Schwarz-Bäume (verbreiteste Variante)&lt;br /&gt;
#* Treaps (flexibelste Variante, siehe Übung)&lt;br /&gt;
#* Splay trees&lt;br /&gt;
#* Andersson Trees (einfachste Variante)&lt;br /&gt;
(#* Skip Lists (schnellste Variante, aber kein Binärbaum))&lt;br /&gt;
&lt;br /&gt;
== Umstrukturieren, so dass Suchbaumbedingung erhalten bleibt: ==&lt;br /&gt;
&lt;br /&gt;
Rotation: elementare Umstrukturierungen&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum_Rotation.png]]&lt;br /&gt;
&lt;br /&gt;
 def rotateRight(node):&lt;br /&gt;
     newRoot = node.left&lt;br /&gt;
     node.left = newRoot.right&lt;br /&gt;
     newRoot.right = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
 def rotateLeft(node):&lt;br /&gt;
     newRoot = node.right&lt;br /&gt;
     node.right = newRoot.left&lt;br /&gt;
     newRoot.left = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
== Balance eines Suchbaumes ==&lt;br /&gt;
&lt;br /&gt;
===Balance eines Baumes zu  definieren:===&lt;br /&gt;
[[Image:Abbildung1.jpg|250px|right]]&lt;br /&gt;
*betrachte &amp;lt;tt&amp;gt;None&amp;lt;/tt&amp;gt; als zusätzlichen Knoten : '''sentinel'''   ([[englische Sprache|engl.]] für ''Wächter'')&lt;br /&gt;
*oder defeniere speziellen sentinel-Knoten&lt;br /&gt;
*RS - Pfades : von &amp;lt;tt&amp;gt;root&amp;lt;/tt&amp;gt; &amp;amp;rarr; ''sentinel''&lt;br /&gt;
*Länge eines Pfades ''P''      '''''|P|'''''&lt;br /&gt;
* &amp;lt;u&amp;gt;die Balance eines Baumes zu defenieren:&amp;lt;/u&amp;gt;&lt;br /&gt;
: '''''B = max P '''''&amp;lt;math&amp;gt; \in &amp;lt;/math&amp;gt; '''''{RS}    |P| - min |P|'''''&lt;br /&gt;
:'''''{RS}'''''  Menge aller RS-Pfade&lt;br /&gt;
;vollständiger Baum:  Balance &amp;lt;math&amp;gt;~=0&amp;lt;/math&amp;gt;&lt;br /&gt;
:Alle Knoten(außer Blättern) haben 2 Kinder.&lt;br /&gt;
;perfect balancierter Baum:  Balance  &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 1&lt;br /&gt;
'''alternative Defenition für perfect balancierte Bäume'''&lt;br /&gt;
:Für jeden Teilbaum gilt es: rechtes und linkes Kind (für jeden Knoten) sind auch wieder perfect balancierte Bäume und ihre Höhe unterscheidet sich höchstens um '''1'''.&lt;br /&gt;
&lt;br /&gt;
===Größe===&lt;br /&gt;
[[Image:baum-v.png|700px|right]]&lt;br /&gt;
====Größe des vollständigen Baumes====&lt;br /&gt;
Ebene K hat 2 &amp;lt;sup&amp;gt;k&amp;lt;/sup&amp;gt; Knoten.&lt;br /&gt;
Falls Tiefe = d , dann N = 2&amp;lt;sup&amp;gt;0&amp;lt;/sup&amp;gt; + 2&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;.....+ 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; = 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1&lt;br /&gt;
&lt;br /&gt;
====Größe des perfect balancierten Baumes====&lt;br /&gt;
Die Tiefe d, kann nicht besser sein als vollständiger Baum&lt;br /&gt;
:'''=&amp;gt;'''N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2 &amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
;schlechteste perfect balancierter Baum : ist ein vollständiger der Tiefe  d - 1 + 1, N &amp;lt;math&amp;gt; \ge&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:'''=&amp;gt;'''  2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1)   &amp;lt;math&amp;gt;~&amp;lt;&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt;)&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
d &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt;  log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; (d + 1)&lt;br /&gt;
&lt;br /&gt;
*Ergibt die Komplexität der Suche im schlechtesten Fall: Anzahl der Vergleiche pro Knoten( = 2 bzw. = 3)&amp;lt;math&amp;gt;\ast&amp;lt;/math&amp;gt;Anzahl der Knoten&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow ~f(N)\le 2d \le 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
*'''perfekt balancierter Baum '''=&amp;gt; AVL-Baum &lt;br /&gt;
*'''balancierter Baum''':&lt;br /&gt;
::&amp;lt;math&amp;gt;\forall  N:d(N)\le c \ast d (N)&amp;lt;/math&amp;gt; und  &amp;lt;math&amp;gt;1 \le c &amp;lt; \mathcal {1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:: d ist die Tiefe von perfekt balancierten Baum&lt;br /&gt;
&lt;br /&gt;
:''Komplexität der Suche'':&amp;lt;math&amp;gt; ~f(N)\le  c\ast 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
:algorithmisch einfacher als perfekt balancierter Baum, aber fast genauso schnell&lt;br /&gt;
&lt;br /&gt;
====Selbst-balancierter Baum====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  def insertTree(node,key):&lt;br /&gt;
    if node is sentinel:       #(None = sentinel)&lt;br /&gt;
        return Node(key)       &lt;br /&gt;
    if node.key == key:&lt;br /&gt;
        return Node&lt;br /&gt;
    if key &amp;lt; node.key:&lt;br /&gt;
        Node = insertTree(node.left, key)&lt;br /&gt;
    else:&lt;br /&gt;
        node.right = insertTree(node.right, key)&lt;br /&gt;
   #optimiere Balance:&lt;br /&gt;
        return node&lt;br /&gt;
&lt;br /&gt;
===Anderson-Bäume===&lt;br /&gt;
&lt;br /&gt;
  class Node:&lt;br /&gt;
    def__init__(self, key):&lt;br /&gt;
        self.key = key&lt;br /&gt;
        self.left = selft.right = None&lt;br /&gt;
        #einfügen:&lt;br /&gt;
        self.level = 1&lt;br /&gt;
&lt;br /&gt;
:''level'' : kodiert Abstand von sentinel&lt;br /&gt;
====Regeln====&lt;br /&gt;
Es gibt vertikale Kanten(parent.level == child.level + 1 ) und horizontale Kanten(parent.level == child.level)&lt;br /&gt;
:*verfeinerter Regel :  ''level kodiert reduzierten Abstand von Setinel''  (d.b. horizontale Kanten werden nicht gezählt)&lt;br /&gt;
::(die nächste zwei Regeln sichern die Balance):&lt;br /&gt;
::*alle RS-Pfade haben die gleiche reduzierte Länge oder ''root'' hat bei allen Pfaden das gleiche Level&lt;br /&gt;
::*kein Pfad hat 2 aufeinanderer folgende horizontale Kanten&lt;br /&gt;
:*nur Kanten zum rechten Kind dürfen horizontal sein&lt;br /&gt;
:*die reduzierte Höhe jedes Blatts ist '''hr=1'''&lt;br /&gt;
&lt;br /&gt;
====Beweis====&lt;br /&gt;
Vereinfachung des Algorithmus&lt;br /&gt;
:'''Satz''':ein Anderson-Baum ist balanciert.&lt;br /&gt;
*1. Sei '''hr'''- die reduzierte Höhe&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; jeder Teilbaum enthält mindestens &amp;lt;math&amp;gt;N\ge 2^{hr} -1 &amp;lt;/math&amp;gt;  Knoten&lt;br /&gt;
&lt;br /&gt;
::a). Blätter : reduzierte Höhe 1 =&amp;gt; &amp;lt;math&amp;gt; N\ge 2^{1} - 1 = 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
::b). inneren Knoten: jeder Unterbaum hat mindestens reduzierte Höhe &amp;lt;math&amp;gt; ~hr - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt;  jeder Unterbaum hat mindestens &amp;lt;math&amp;gt; ~2^{hr} -1 &amp;lt;/math&amp;gt; Knoten&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt;N \ge 2 (2^{hr-1} -1)+1 = 2^w - 2 + 1 = 2^{hr} -1&amp;lt;/math&amp;gt;&lt;br /&gt;
::::alle RS-Pfade heben gleiche Länge&lt;br /&gt;
[[Image:Anderson.png|700px|right]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
*2. Kein Pfad hat 2 aufeinanderfolgende horizontal Kanten&lt;br /&gt;
:::&amp;lt;math&amp;gt;\Rightarrow  d \le 2 hr + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
*3. zusammen:&lt;br /&gt;
:&amp;lt;math&amp;gt;N \ge 2^{\frac {d}{2}} - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;log_{2}{ N}+1\ge log 2^{\frac {d}{2}} - 1 \ge \frac {d}{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; ~d &amp;lt; 2 log_{2}{(N + 1)}&amp;lt;/math&amp;gt;     - balancierter Baum&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
:'''Suchzeit''' : &amp;lt;math&amp;gt; ~ f(N) = \mathcal{O}(log{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Wie erreicht man die Balance===&lt;br /&gt;
====Rotation====&lt;br /&gt;
[[Image:Abbildung4.jpg|text-top|350px|Illustration von Rotation]]&lt;br /&gt;
&lt;br /&gt;
   def rotateRight (node):&lt;br /&gt;
      root = node.left&lt;br /&gt;
      node.left = node.right&lt;br /&gt;
      root.right = node&lt;br /&gt;
      return root&lt;br /&gt;
   def rotateLeft(node):&lt;br /&gt;
      root = node.right&lt;br /&gt;
      node.right = node.left&lt;br /&gt;
      root.left = node&lt;br /&gt;
      return root&lt;br /&gt;
&lt;br /&gt;
====Optimierung der Balance====&lt;br /&gt;
   &lt;br /&gt;
&lt;br /&gt;
       if node.left is not sentinel and node.level==node.left.level:&lt;br /&gt;
            node &amp;gt; rotateRight(node)&lt;br /&gt;
       if node.right is not sentinel and node.right.right is  not sentinel and node.level==rotate.right.right.level:&lt;br /&gt;
            node = rotateLeft(node)&lt;br /&gt;
            node.level += 1&lt;/div&gt;</summary>
		<author><name>Nata</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1502</id>
		<title>Suchen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1502"/>
				<updated>2008-05-29T00:27:27Z</updated>
		
		<summary type="html">&lt;p&gt;Nata: /* Rotation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Es wäre super wenn jemand ganz kurz schreiben könnte, was am Donnerstag zu den Funktionen treeInsert/Remove/HasKey gemacht wurde, was man ja für den aktuellen Übungszettel braucht. Danke :-)&amp;lt;br /&amp;gt;&lt;br /&gt;
/edit: an eine Funktion &amp;quot;HasKey&amp;quot; kann ich mich aus der Vorlesung nicht erinnern. Wenn die jemand hat, bitte eintragen. - Protokollant&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--Hallo, ich kenne leider deinen Namen nicht, ich habe den Wiki-Eintrag von letztem Donnerstag gemacht.Da das Thema 'Dynamisches Array' noch Thema von letzter Woche war, hab ich die Wiederholung der amortisierten Kosten von diesem Mittwoch noch bei Effizienz eingetragen, du musst es also nicht noch mal in deinem Wiki eintragen. Du kannst dir aber gerne mal anschauen was ich geschrieben habe, vielleicht fallen dir noch ein paar Sachen ein, die man hätte besser machen können. lg, Franziska.--&amp;gt;&lt;br /&gt;
Das Suchen ist eine grundlegende Operation in der Informatik. Viele Probleme in der Informatik können auf Suchaufgaben zurückgeführt werden.&lt;br /&gt;
&lt;br /&gt;
Gemeint ist mit Suchen das Wiederauffinden einer oder mehrerer Datensätze aus einer Menge von früher gespeicherten Datensätzen. Ein paar einleitende Worte zum Suchproblem findet man [http://de.wikipedia.org/wiki/Suche hier].&lt;br /&gt;
&lt;br /&gt;
== Überblick verschiedener Suchmethoden ==&lt;br /&gt;
&lt;br /&gt;
Um sich der Vielseitigkeit von Suchproblemen bewusst zu werden, ist es sinnvoll, sich einen Überblick über verschiedene Suchmethoden zu verschaffen. &lt;br /&gt;
&lt;br /&gt;
Hier sei auch auf einen bereits existierenden Wikipedia-Artikel zu [http://de.wikipedia.org/wiki/Suchverfahren Suchverfahren] verwiesen.&lt;br /&gt;
&lt;br /&gt;
Allen gemeinsam ist die grundlegende Aufgabe, ein Datenelement mit bestimmten Eigenschaften aus einer großen Menge von Datenelementen zu selektieren.&lt;br /&gt;
Dies kann, natürlich ohne jeden Anspruch auf Vollständigkeit, nach einer der jetzt diskutierten Methoden geschehen:&lt;br /&gt;
&lt;br /&gt;
* '''Schlüsselsuche''': meint das Suchen von Elementen mit bestimmtem Schlüssel; ein klassisches Beispiel wäre das Suchen in einem Wörterbuch,  die Schlüssel entsprechen hier den Wörtern, die Datensätze wären die zu den Wörtern gehörigen Eintragungen.&lt;br /&gt;
&lt;br /&gt;
* '''Bereichssuche''': Im Allgemeinen meint die Bereichssuche in n-Dimensionen die Selektion von Elementen mit Eigenschaften aus einem bestimmten n-dimensionalen Volumen. Im eindimensionalen Fall will man alle Elemente finden, deren Eigenschaft(en) in einem bestimmten Intervall liegen. Die Verallgemeinerung auf n-Dimensionen ist offensichtlich. Ein Beispiel für die Bereichssuche in einer 3D-Kugel wäre ein Handy mit Geolokalisierung, welches alle Restaurants in einem Umkreis von 500m findet. Lineare Ungleichungen werden graphisch durch [http://de.wikipedia.org/wiki/Hyperebene Hyperebenen] repräsentiert. In 2D sind diese Hyperebenen Geraden. Die Ungleichungen können dann den Lösungsraum in irgendeiner Form begrenzen.&lt;br /&gt;
&lt;br /&gt;
* '''Ähnlichkeitssuche''': Finde Elemente, die gegebenen Eigenschaften möglichst ähnlich sind. Ein prominentes Beispiel ist Google (=Ähnlichkeit zwischen Suchbegriffen und Dokumenten) oder das Suchen des nächstengelegenen Restaurants (Ähnlichkeit zwischen eigener Position und Position des Restaurants). Ein wichtigster Spezialfall ist die ''nächste-nachbar Suche''.&lt;br /&gt;
&lt;br /&gt;
* '''Graphensuche''': Hier wäre beispielsweise das Problem optimaler Wege zu nennen (Navigationssuche). Dieser Punkt wird später im Verlauf der Vorlesung noch einmal aufgegriffen werden.&lt;br /&gt;
&lt;br /&gt;
Im jetzt Folgenden wird nur noch die ''Schlüsselsuche'' betrachtet werden.&lt;br /&gt;
&lt;br /&gt;
==Sequentielle Suche==&lt;br /&gt;
&lt;br /&gt;
Die ''sequentielle'' oder ''lineare'' Suche ist die 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 = sequentialSearch(a, key) &lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq &amp;lt;/math&amp;gt; foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def sequentialSearch(a, key):&lt;br /&gt;
    for i in range(len(a)):&lt;br /&gt;
        if a[i] == key:  # bzw. allgemeiner a[i].key == key &lt;br /&gt;
            return i&lt;br /&gt;
    return -1&lt;br /&gt;
&lt;br /&gt;
Wir wollen jetzt die Komplexität dieses Algorithmus bestimmen, wobei die Problemgröße durch &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt; gegeben ist. &lt;br /&gt;
&lt;br /&gt;
Dabei nimmt man an, dass der innerste Vergleich (a[i] == key) jeweils &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt; ist (diese Annahme könnte verletzt sein, wenn der Vergleichsoperator überladen ist und dadurch eine höhere Komplexität hat). Dieser Vergleich wird in der for-Schleife jeweils N-mal durchgeführt (&amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt;), so dass man nach der Verschachtelungsregel eine gesamte Komplexität von &amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt; erhält.&lt;br /&gt;
&lt;br /&gt;
Der Name ''lineare'' Suche rührt von diesem linearen Anwachsen der Komplexität mit der Arraygröße her.&lt;br /&gt;
&lt;br /&gt;
==Binäre Suche==&lt;br /&gt;
&lt;br /&gt;
Wie wir weiter unten zeigen werden, gestattet es diese Suchmethode, die Gesamtdauer der Suche in großen Datensätzen beträchtlich zu verringern. Die Methode beruht auf dem [http://de.wikipedia.org/wiki/Divide_and_Conquer Divide and Conquer-Prinzip], wobei die Suche in jedem Schritt rekursiv auf eine Hälfte des Datensatzes eingeschränkt wird. Weitere Details zur Methode sind [http://de.wikipedia.org/wiki/Bin%C3%A4re_Suche hier] zu finden. &lt;br /&gt;
&lt;br /&gt;
Die Methode ist nur dann anwendbar beziehungsweise effektiv, wenn folgendes gilt:&lt;br /&gt;
&lt;br /&gt;
# Auf der Eigenschaft der Daten, die zur Suche verwendet wird, ist eine Ordnung im Sinne von &amp;lt; oder &amp;gt; definiert.&lt;br /&gt;
# Wir wollen uns auf Datensätze beschränken, die schon fertig aufgebaut sind, in die also keine neuen Elemente mehr eingefügt werden, wenn man mit dem Suchen beginnt. Ist dies nicht der Fall, so ist unter Umständen die Implementierung über einen [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum Binärbaum] (siehe auch weiter unten) geschickter. &lt;br /&gt;
&lt;br /&gt;
Der folgende Algorithmus zeigt eine beispielhafte Implementierung der Methode (wir setzen wieder &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt;):&lt;br /&gt;
&lt;br /&gt;
 a = [...,...]     # array&lt;br /&gt;
 a.sort()   # sortiere über Ordnung des Schlüssels&lt;br /&gt;
 foundIndex = binSearch(a, key, 0, len(a))  # (Array, Schlüssel, von wo bis wo suchen im Array)&lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq&amp;lt;/math&amp;gt;  foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def binSearch(a, key, start, end):  # start ist 1. Index, end ist letzter Index + 1&lt;br /&gt;
    size = end - start   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if size &amp;lt;= 0:   # Bereich leer?  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return -1   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    center = (start + end)/2   # Integer Division, Ergebnis wird abgerundet, wichtig für ganzzahlige Indizes &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if a[center] == key:  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return center  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    elif a[center] &amp;lt; key:  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return binSearch(a, key, center + 1, end)  # Rekursion&lt;br /&gt;
    else:&lt;br /&gt;
        return binSearch(a, key, start + 1, center)  # Rekursion&lt;br /&gt;
&lt;br /&gt;
Zur Berechnung der Komplexität dieses Algorithmus vernachlässigen wir zunächst den Aufwand, den die Sortierung weiter oben verursacht. Dieser Schritt mag oder mag nicht zulässig sein.&lt;br /&gt;
&lt;br /&gt;
Nach der Sequenzregel haben auch alle &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;.  Es bleibt die Komplexität der Rekursion zu berechnen. Die gesamte Komplexität des Algorithmus (jetzt als Funktion f bezeichnet) setzt sich zusammen aus den oben erwähnten &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen sowie der Rekursion und ist &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f(N) = \mathcal{O}(1) + f(N/2) = \mathcal{O}(1) + \mathcal{O}(1) + f(N/4) = ... = \underbrace{\mathcal{O}(1) + ... + \mathcal{O}(1) + \underbrace{f(0)}_{\mathcal{O}(1)\, \rightarrow \,\mathrm{size-Abfrage}}}_{n+1 \,\mathrm{Terme}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Falls jetzt gilt &amp;lt;math&amp;gt; N = 2^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \rightarrow f(N) = \mathcal{O}(1) \cdot \mathcal{O}(n+1) = \mathcal{O}(n) = \mathcal{O}(\lg N) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Für große Datenmengen ist die ''binäre Suche'' also weit effizienter als die ''lineare Suche''. Verdoppelt sich beispielsweise die zu durchsuchende Datenmenge, so verdoppelt sich der Aufwand für die ''sequentielle Suche'' - bei der ''binären Suche'' hingegen benötigt man lediglich eine zusätzliche Vergleichsoperation. &lt;br /&gt;
&lt;br /&gt;
Für kleine Daten (&amp;lt;math&amp;gt; N = 4,\, 5 &amp;lt;/math&amp;gt;) ist die ''sequentielle Suche'' jedoch schneller als die ''binäre Suche'', da hier die rekursiven Funktionsaufrufe teurer als das Mehr an Vergleichen sind. Ein anderer ungünstiger Fall ist gegeben, wenn nur sehr wenige Suchanfragen erfolgen (weniger als &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; viele). Dann wird der Aufwand durch das Sortieren des Arrays dominiert, ist also &amp;lt;math&amp;gt;\mathcal{O}(N \lg N) &amp;lt;/math&amp;gt;. Auch dann ist sequentielle Suche vorzuziehen.&lt;br /&gt;
&lt;br /&gt;
Eine relativ einfache Möglichkeit, die ''binäre Suche'' zu verbessern, ist die sogenannte ''Interpolationssuche''. Hierbei wird die neue Position für die Suche, also die Mitte des Arrays, durch eine Schätzung ersetzt, die angibt, wo sich der Schlüssel innerhalb des Arrays befinden könnte. Bei der Suche in einem Telefonbuch nach dem Namen Zebra würde man ja auch nicht in der Mitte anfangen. Näheres hierzu im Buch von ''Sedgewick''.&lt;br /&gt;
&lt;br /&gt;
Um sich den Algorithmus der ''binären Suche'' klar zu machen, ist es instruktiv, sich die folgende Tabelle genauer anzusehen, die die sukzessive Belegung der Variablen bei verschiedenen Anfragen beschreibt. Die Testfälle wurden nach dem Prinzip des ''domain partitioning'' gewählt. Das zugehörige Array hat die Einträge&lt;br /&gt;
&lt;br /&gt;
 a = [2, 3, 4, 5, 6]&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align:center&amp;quot; border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;5&amp;quot; cellspacing=&amp;quot;0&amp;quot; &lt;br /&gt;
! gesuchter key   !!  start      !! end  !! size !! center !! return &amp;lt;br/&amp;gt; (-1 oder index)  !! Kommentare  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 4     ||0            || 5    ||  5   || 2   ||  2         || gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 2     || 0           || 5    ||  5   || 2   ||            || linker Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||           &lt;br /&gt;
|-&lt;br /&gt;
|       ||  0          || 1    ||  1   || 0   ||  0         || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 1     ||0            || 5    ||  5   || 2   ||            || links außerhalb&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 1    ||  1   || 0   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 0    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 6     ||0            || 5    ||  5   || 2   ||            || rechter Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    || 2    || 4   || 4          || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 5     ||0            || 5    ||  5   || 2   ||            || typischer Fall&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||3            || 5    ||  2   || 4   ||            ||  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       || 3           || 4    ||  1   || 3   || 3          || gefunden&lt;br /&gt;
|- &lt;br /&gt;
| 7     ||0            || 5    ||  5   || 2   ||            || rechts außerhalb        &lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    ||  2   || 4   ||            ||&lt;br /&gt;
|-&lt;br /&gt;
|       ||5            || 5    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Suche in einem Binärbaum ==&lt;br /&gt;
&lt;br /&gt;
Eine kurze Einführung in Binärbäume findet man [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum hier].&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum.png|text-top|300x300px|Zur Illustration von Bäumen]]&lt;br /&gt;
&lt;br /&gt;
Bäume sind zweidimensional verkettete Strukturen. Sie gehören zu den fundamentalen Datenstrukturen in der Informatik. Da man in Bäumen nicht nur Daten speichern kann, sondern auch relevante Beziehungen der Daten untereinander, festgelegt über eine Ordnung auf der vergleichenden Dateneigenschaft (''Schlüssel''), eignen sich Bäume also insbesondere, um gesuchte Daten schnell wieder auffinden zu können.&lt;br /&gt;
&lt;br /&gt;
Ein ''Binärbaum'' wie oben skizziert besteht aus einer Menge von ''Knoten'', die untereinander durch ''Kanten'' verbunden sind. Jeder Knoten hat einen linken und einen rechten Unterbaum, der auch leer sein kann (in Python ließe sich dies mit ''None'' implementieren). Führt eine Kante von Knoten A zu Knoten B, so heißt A Vater von B und B Kind von A. Es gibt genau einen Knoten ohne Vater, den man ''Wurzel'' nennt. Knoten ohne Kinder heißen ''Blätter''.&lt;br /&gt;
&lt;br /&gt;
Ein ''Suchbaum'' hat zusätzlich die Eigenschaft, dass die Schlüssel jedes Knotens sortiert sind. Alle Schlüssel im linken Unterbaum sind kleiner, alle Schlüssel im rechten Unterbaum sind größer als ihr Vater. Wir wollen hierbei annehmen, dass jeder Schlüssel pro Datensatz nur einmal vorkommt, da sich sonst die &amp;gt;- oder &amp;lt;-Relation nicht mehr strikt erfüllen ließe.&lt;br /&gt;
&lt;br /&gt;
Um in einem Baum suchen zu können, wollen wir von zwei Annahmen ausgehen:&lt;br /&gt;
# Einfügen und Suchen im Baum wechseln sich ab. (Wenn das Suchen erst beginnt, nachdem alle Einfügungen erfolgt sind, wäre ein dynamisches Array mit binärer Suche wie oben wesentlich einfacher.)&lt;br /&gt;
# Der Schlüssel, der die Anordnung bestimmt, kennt eine [http://de.wikipedia.org/wiki/Ordnungsrelation Ordnung] (&amp;lt;-Relation oder &amp;gt;-Relation).&lt;br /&gt;
&lt;br /&gt;
Der folgende Python-Code zeigt beispielhaft, wie man in einem Suchbaum suchen könnte. Der Konstruktor für einen Knoten des Suchbaums ließe sich zum Beispiel so implementieren:&lt;br /&gt;
 &lt;br /&gt;
 class Node:&lt;br /&gt;
     def __init__(self, key):&lt;br /&gt;
         self.key = key&lt;br /&gt;
         self.left = self.right = None&lt;br /&gt;
 &lt;br /&gt;
 root = ...    # Wurzel des Suchbaums&lt;br /&gt;
 nodeFound = treeSearch(root, key)   # None, falls nichts gefunden&lt;br /&gt;
 &lt;br /&gt;
 def treeSearch(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return None&lt;br /&gt;
     elif node.key == key:&lt;br /&gt;
         return node&lt;br /&gt;
     elif key &amp;lt; node.key:&lt;br /&gt;
         return treeSearch(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         return treeSearch(node.right, key)&lt;br /&gt;
&lt;br /&gt;
Daraus resultiert der folgende Suchalgorithmus:&lt;br /&gt;
&lt;br /&gt;
 def treeSort(node,array):     # dynamisches Array als 2. Argument&lt;br /&gt;
     if node is None:     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
         return None:&lt;br /&gt;
     treeSort(node.left, array)     # rekursiv&lt;br /&gt;
     array.append(node.key)     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
     treeSort(node.right, array)     # rekursiv&lt;br /&gt;
&lt;br /&gt;
Komplexität:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 f(N)=\mathcal{O}(1)+f(N_\mathrm{left})+f(N_\mathrm{right})=\mathcal{O}(1)+\mathcal{O}(1)+f(N_\mathrm{leftleft})+f(N_\mathrm{leftright})+\mathcal{O}(1)+f(N_\mathrm{rightleft})&lt;br /&gt;
 +f(N_\mathrm{leftright})=N\ast\mathcal{O}(1)=\mathcal{O}(N)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sortier-Pseudocode:&lt;br /&gt;
&lt;br /&gt;
 Sortieren:&lt;br /&gt;
     (Array) a     # unsortiert&lt;br /&gt;
     (tree) t     # zunächst leer&lt;br /&gt;
 (dynamisches Array) r     # später sortiert&lt;br /&gt;
 for e in a:&lt;br /&gt;
     t = treeInsert(t, e)&lt;br /&gt;
 treeSort(t, r)&lt;br /&gt;
&lt;br /&gt;
== Insert ==&lt;br /&gt;
&lt;br /&gt;
Was passiert wenn der key (Schlüssel) schon vorhanden ist?&lt;br /&gt;
* error (exception)&lt;br /&gt;
* nichts einfügen&lt;br /&gt;
* nichts einfügen aber einen boolean return zurückgeben (einfügen=true, false)&lt;br /&gt;
* nochmals eingefügt (z.B. in der Node Klasse)&lt;br /&gt;
&lt;br /&gt;
Wobei die ersten 3 Punkte zur Mengensemantik gehören und der letzte eine Multimenge ist.&lt;br /&gt;
&lt;br /&gt;
Algorithmus im zweiten Fall (nichts einfügen):&lt;br /&gt;
&lt;br /&gt;
 def treeInsert(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return Node(key)     # Alternative Schreibweise: node = Node(key)&lt;br /&gt;
     if node.key == key:      # und dann:&lt;br /&gt;
         return node          # pass&lt;br /&gt;
     elif key &amp;lt; node.key:     # links im Baum&lt;br /&gt;
         node.left = treeInsert(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         node.right = treeInsert(node.right, key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Remove ==&lt;br /&gt;
Fälle:&lt;br /&gt;
# key (bzw. Knoten der key enthält) ist ein Blatt =&amp;gt; einfach löschen&lt;br /&gt;
# node hat &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; linken Unterbaum oder &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; rechten Unterbaum =&amp;gt; durch Unterbaum ersetzen&lt;br /&gt;
# node hat beide Unterbäume:&lt;br /&gt;
#* Suche Vorgänger: max k &amp;lt; key (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; keys); Vorgänger ist immer ein Fall 1 oder Fall 2&lt;br /&gt;
#*:=&amp;gt; ersetze node durch Vorgänger und entferne Vorgänger&lt;br /&gt;
&lt;br /&gt;
 def treePredecessor(node):     # wird nur bei Fall 3 aufgerufen&lt;br /&gt;
     node = node.left&lt;br /&gt;
     while node.right is not None:&lt;br /&gt;
         node = node.right&lt;br /&gt;
     return node&lt;br /&gt;
 &lt;br /&gt;
 def treeRemove(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return node&lt;br /&gt;
     if key &amp;lt; node.key:&lt;br /&gt;
         node.left = treeRemove(node.left, key)&lt;br /&gt;
     elif key &amp;gt; node.key:&lt;br /&gt;
         node.right = treeRemove(node.right, key)&lt;br /&gt;
     else:&lt;br /&gt;
         if node.left is None and node.right is None:     # Fall 1&lt;br /&gt;
             node = None&lt;br /&gt;
         elif node.left is None:     # Fall 2&lt;br /&gt;
             node = node.right       # +&lt;br /&gt;
         elif node.right is None:    # Fall 2&lt;br /&gt;
             node = node.left&lt;br /&gt;
         else:&lt;br /&gt;
             pred = treePredecessor(node)&lt;br /&gt;
             node.key = pred.key&lt;br /&gt;
             node.left = treeRemove(node.left, pred.key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Komplexitätsanalyse ==&lt;br /&gt;
&lt;br /&gt;
* Pfad (Zwischen node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;): &lt;br /&gt;
**Folge von Knoten (node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt;,...,node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt;), sodass:&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;ki&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;ki+1&amp;lt;/sub&amp;gt; haben eine gemeinsame Kante.&lt;br /&gt;
[[Image:Baum_Pfad.png]]&lt;br /&gt;
(Ein Baum ist ein Graph, indem es zwischen beliebigen Knoten stets genau einen Pfad gibt.)&lt;br /&gt;
&lt;br /&gt;
* Länge eines Pfades: Anzahl der Kanten = Anzahl der Knoten - 1&lt;br /&gt;
* Tiefe eines Knotens: Pfadlänge vom Knoten zur Wurzel des Baumes.&lt;br /&gt;
* Tiefe des Baumes: maximale Tiefe eines Knotens&lt;br /&gt;
* Balance eines Baumes: maximale Tiefe(k) - minimale Tiefe(k) (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; Blätter)&lt;br /&gt;
&lt;br /&gt;
== Ungünstigster Fall von treeSearch ==&lt;br /&gt;
&lt;br /&gt;
Komplexität von treeSearch = Länge des Pfades zum Knoten wo &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; gefunden wird, oder erkannt wird, dass &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; nicht im Baum ist.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; wird nicht gefunden, aber für diese Entscheidung muss der Längste Pfad vollständig durchlaufen werden.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(T)&amp;lt;/math&amp;gt; wo T = Tiefe des Baumes&lt;br /&gt;
*: [1,2,3,4,5]:&amp;lt;br /&amp;gt; [[Image:Balance.png]]&lt;br /&gt;
*: Tiefe T = 4, Balance = 4&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; wo N = Anzahl der Elemente.&lt;br /&gt;
&lt;br /&gt;
== Aufgabe ==&lt;br /&gt;
&lt;br /&gt;
Minimiere Balance (erzeuge balancierten Baum):&lt;br /&gt;
# Einfügen in geschickter Reihenfolge (siehe Übungsaufgabe)&lt;br /&gt;
# Selbstbalancierter Baum:&lt;br /&gt;
#* Überprüfen der Balance nach jedem Einfügen&lt;br /&gt;
#* Umstrukturieren des Baumes, falls Balance &amp;gt; 1 (Suchbaum-Bedingung muss erhalten bleiben)&lt;br /&gt;
#* AVL-Bäume (älteste Variante)&lt;br /&gt;
#* Rot-Schwarz-Bäume (verbreiteste Variante)&lt;br /&gt;
#* Treaps (flexibelste Variante, siehe Übung)&lt;br /&gt;
#* Splay trees&lt;br /&gt;
#* Andersson Trees (einfachste Variante)&lt;br /&gt;
(#* Skip Lists (schnellste Variante, aber kein Binärbaum))&lt;br /&gt;
&lt;br /&gt;
== Umstrukturieren, so dass Suchbaumbedingung erhalten bleibt: ==&lt;br /&gt;
&lt;br /&gt;
Rotation: elementare Umstrukturierungen&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum_Rotation.png]]&lt;br /&gt;
&lt;br /&gt;
 def rotateRight(node):&lt;br /&gt;
     newRoot = node.left&lt;br /&gt;
     node.left = newRoot.right&lt;br /&gt;
     newRoot.right = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
 def rotateLeft(node):&lt;br /&gt;
     newRoot = node.right&lt;br /&gt;
     node.right = newRoot.left&lt;br /&gt;
     newRoot.left = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
== Balance eines Suchbaumes ==&lt;br /&gt;
&lt;br /&gt;
===Balance eines Baumes zu  definieren:===&lt;br /&gt;
[[Image:Abbildung1.jpg|250px|right]]&lt;br /&gt;
*betrachte &amp;lt;tt&amp;gt;None&amp;lt;/tt&amp;gt; als zusätzlichen Knoten : '''sentinel'''   ([[englische Sprache|engl.]] für ''Wächter'')&lt;br /&gt;
*oder defeniere speziellen sentinel-Knoten&lt;br /&gt;
*RS - Pfades : von &amp;lt;tt&amp;gt;root&amp;lt;/tt&amp;gt; &amp;amp;rarr; ''sentinel''&lt;br /&gt;
*Länge eines Pfades ''P''      '''''|P|'''''&lt;br /&gt;
* &amp;lt;u&amp;gt;die Balance eines Baumes zu defenieren:&amp;lt;/u&amp;gt;&lt;br /&gt;
: '''''B = max P '''''&amp;lt;math&amp;gt; \in &amp;lt;/math&amp;gt; '''''{RS}    |P| - min |P|'''''&lt;br /&gt;
:'''''{RS}'''''  Menge aller RS-Pfade&lt;br /&gt;
;vollständiger Baum:  Balance &amp;lt;math&amp;gt;~=0&amp;lt;/math&amp;gt;&lt;br /&gt;
:Alle Knoten(außer Blättern) haben 2 Kinder.&lt;br /&gt;
;perfect balancierter Baum:  Balance  &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 1&lt;br /&gt;
'''alternative Defenition für perfect balancierte Bäume'''&lt;br /&gt;
:Für jeden Teilbaum gilt es: rechtes und linkes Kind (für jeden Knoten) sind auch wieder perfect balancierte Bäume und ihre Höhe unterscheidet sich höchstens um '''1'''.&lt;br /&gt;
&lt;br /&gt;
===Größe===&lt;br /&gt;
[[Image:baum-v.png|700px|right]]&lt;br /&gt;
====Größe des vollständigen Baumes====&lt;br /&gt;
Ebene K hat 2 &amp;lt;sup&amp;gt;k&amp;lt;/sup&amp;gt; Knoten.&lt;br /&gt;
Falls Tiefe = d , dann N = 2&amp;lt;sup&amp;gt;0&amp;lt;/sup&amp;gt; + 2&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;.....+ 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; = 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1&lt;br /&gt;
&lt;br /&gt;
====Größe des perfect balancierten Baumes====&lt;br /&gt;
Die Tiefe d, kann nicht besser sein als vollständiger Baum&lt;br /&gt;
:'''=&amp;gt;'''N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2 &amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
;schlechteste perfect balancierter Baum : ist ein vollständiger der Tiefe  d - 1 + 1, N &amp;lt;math&amp;gt; \ge&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:'''=&amp;gt;'''  2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1)   &amp;lt;math&amp;gt;~&amp;lt;&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt;)&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
d &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt;  log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; (d + 1)&lt;br /&gt;
&lt;br /&gt;
*Ergibt die Komplexität der Suche im schlechtesten Fall: Anzahl der Vergleiche pro Knoten( = 2 bzw. = 3)&amp;lt;math&amp;gt;\ast&amp;lt;/math&amp;gt;Anzahl der Knoten&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow ~f(N)\le 2d \le 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
*'''perfekt balancierter Baum '''=&amp;gt; AVL-Baum &lt;br /&gt;
*'''balancierter Baum''':&lt;br /&gt;
::&amp;lt;math&amp;gt;\forall  N:d(N)\le c \ast d (N)&amp;lt;/math&amp;gt; und  &amp;lt;math&amp;gt;1 \le c &amp;lt; \mathcal {1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:: d ist die Tiefe von perfekt balancierten Baum&lt;br /&gt;
&lt;br /&gt;
:''Komplexität der Suche'':&amp;lt;math&amp;gt; ~f(N)\le  c\ast 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
:algorithmisch einfacher als perfekt balancierter Baum, aber fast genauso schnell&lt;br /&gt;
&lt;br /&gt;
====Selbst-balancierter Baum====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  def insertTree(node,key):&lt;br /&gt;
    if node is sentinel:       #(None = sentinel)&lt;br /&gt;
        return Node(key)       &lt;br /&gt;
    if node.key == key:&lt;br /&gt;
        return Node&lt;br /&gt;
    if key &amp;lt; node.key:&lt;br /&gt;
        Node = insertTree(node.left, key)&lt;br /&gt;
    else:&lt;br /&gt;
        node.right = insertTree(node.right, key)&lt;br /&gt;
   #optimiere Balance:&lt;br /&gt;
        return node&lt;br /&gt;
&lt;br /&gt;
===Anderson-Bäume===&lt;br /&gt;
&lt;br /&gt;
  class Node:&lt;br /&gt;
    def__init__(self, key):&lt;br /&gt;
        self.key = key&lt;br /&gt;
        self.left = selft.right = None&lt;br /&gt;
        #einfügen:&lt;br /&gt;
        self.level = 1&lt;br /&gt;
&lt;br /&gt;
:''level'' : kodiert Abstand von sentinel&lt;br /&gt;
====Regeln====&lt;br /&gt;
Es gibt vertikale Kanten(parent.level == child.level + 1 ) und horizontale Kanten(parent.level == child.level)&lt;br /&gt;
:*verfeinerter Regel :  ''level kodiert reduzierten Abstand von Setinel''  (d.b. horizontale Kanten werden nicht gezählt)&lt;br /&gt;
::(die nächste zwei Regeln sichern die Balance):&lt;br /&gt;
::*alle RS-Pfade haben die gleiche reduzierte Länge oder ''root'' hat bei allen Pfaden das gleiche Level&lt;br /&gt;
::*kein Pfad hat 2 aufeinanderer folgende horizontale Kanten&lt;br /&gt;
:*nur Kanten zum rechten Kind dürfen horizontal sein&lt;br /&gt;
:*die reduzierte Höhe jedes Blatts ist '''hr=1'''&lt;br /&gt;
&lt;br /&gt;
====Beweis====&lt;br /&gt;
Vereinfachung des Algorithmus&lt;br /&gt;
:'''Satz''':ein Anderson-Baum ist balanciert.&lt;br /&gt;
*1. Sei '''hr'''- die reduzierte Höhe&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; jeder Teilbaum enthält mindestens &amp;lt;math&amp;gt;N\ge 2^{hr} -1 &amp;lt;/math&amp;gt;  Knoten&lt;br /&gt;
&lt;br /&gt;
::a). Blätter : reduzierte Höhe 1 =&amp;gt; &amp;lt;math&amp;gt; N\ge 2^{1} - 1 = 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
::b). inneren Knoten: jeder Unterbaum hat mindestens reduzierte Höhe &amp;lt;math&amp;gt; ~hr - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt;  jeder Unterbaum hat mindestens &amp;lt;math&amp;gt; ~2^{hr} -1 &amp;lt;/math&amp;gt; Knoten&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt;N \ge 2 (2^{hr-1} -1)+1 = 2^w - 2 + 1 = 2^{hr} -1&amp;lt;/math&amp;gt;&lt;br /&gt;
::::alle RS-Pfade heben gleiche Länge&lt;br /&gt;
[[Image:Anderson.png|700px|right]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
*2. Kein Pfad hat 2 aufeinanderfolgende horizontal Kanten&lt;br /&gt;
:::&amp;lt;math&amp;gt;\Rightarrow  d \le 2 hr + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
*3. zusammen:&lt;br /&gt;
:&amp;lt;math&amp;gt;N \ge 2^{\frac {d}{2}} - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;log_{2}{ N}+1\ge log 2^{\frac {d}{2}} - 1 \ge \frac {d}{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; ~d &amp;lt; 2 log_{2}{(N + 1)}&amp;lt;/math&amp;gt;     - balancierter Baum&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
:'''Suchzeit''' : &amp;lt;math&amp;gt; ~ f(N) = \mathcal{O}(log{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Wie erreicht man die Balance===&lt;br /&gt;
====Rotation====&lt;br /&gt;
[[Image:Abbildung4.jpg|text-top|350px|Illustration von Rotation]]&lt;br /&gt;
&amp;lt;br clear=&amp;quot;both&amp;quot; /&amp;gt;&lt;br /&gt;
   def rotateRight (node):&lt;br /&gt;
      root = node.left&lt;br /&gt;
      node.left = node.right&lt;br /&gt;
      root.right = node&lt;br /&gt;
      return root&lt;br /&gt;
   def rotateLeft(node):&lt;br /&gt;
      root = node.right&lt;br /&gt;
      node.right = node.left&lt;br /&gt;
      root.left = node&lt;br /&gt;
      return root&lt;br /&gt;
&lt;br /&gt;
====Optimierung der Balance====&lt;br /&gt;
   &lt;br /&gt;
&lt;br /&gt;
       if node.left is not sentinel and node.level==node.left.level:&lt;br /&gt;
            node &amp;gt; rotateRight(node)&lt;br /&gt;
       if node.right is not sentinel and node.right.right is  not sentinel and node.level==rotate.right.right.level:&lt;br /&gt;
            node = rotateLeft(node)&lt;br /&gt;
            node.level += 1&lt;/div&gt;</summary>
		<author><name>Nata</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1501</id>
		<title>Suchen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1501"/>
				<updated>2008-05-29T00:26:35Z</updated>
		
		<summary type="html">&lt;p&gt;Nata: /* Rotation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Es wäre super wenn jemand ganz kurz schreiben könnte, was am Donnerstag zu den Funktionen treeInsert/Remove/HasKey gemacht wurde, was man ja für den aktuellen Übungszettel braucht. Danke :-)&amp;lt;br /&amp;gt;&lt;br /&gt;
/edit: an eine Funktion &amp;quot;HasKey&amp;quot; kann ich mich aus der Vorlesung nicht erinnern. Wenn die jemand hat, bitte eintragen. - Protokollant&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--Hallo, ich kenne leider deinen Namen nicht, ich habe den Wiki-Eintrag von letztem Donnerstag gemacht.Da das Thema 'Dynamisches Array' noch Thema von letzter Woche war, hab ich die Wiederholung der amortisierten Kosten von diesem Mittwoch noch bei Effizienz eingetragen, du musst es also nicht noch mal in deinem Wiki eintragen. Du kannst dir aber gerne mal anschauen was ich geschrieben habe, vielleicht fallen dir noch ein paar Sachen ein, die man hätte besser machen können. lg, Franziska.--&amp;gt;&lt;br /&gt;
Das Suchen ist eine grundlegende Operation in der Informatik. Viele Probleme in der Informatik können auf Suchaufgaben zurückgeführt werden.&lt;br /&gt;
&lt;br /&gt;
Gemeint ist mit Suchen das Wiederauffinden einer oder mehrerer Datensätze aus einer Menge von früher gespeicherten Datensätzen. Ein paar einleitende Worte zum Suchproblem findet man [http://de.wikipedia.org/wiki/Suche hier].&lt;br /&gt;
&lt;br /&gt;
== Überblick verschiedener Suchmethoden ==&lt;br /&gt;
&lt;br /&gt;
Um sich der Vielseitigkeit von Suchproblemen bewusst zu werden, ist es sinnvoll, sich einen Überblick über verschiedene Suchmethoden zu verschaffen. &lt;br /&gt;
&lt;br /&gt;
Hier sei auch auf einen bereits existierenden Wikipedia-Artikel zu [http://de.wikipedia.org/wiki/Suchverfahren Suchverfahren] verwiesen.&lt;br /&gt;
&lt;br /&gt;
Allen gemeinsam ist die grundlegende Aufgabe, ein Datenelement mit bestimmten Eigenschaften aus einer großen Menge von Datenelementen zu selektieren.&lt;br /&gt;
Dies kann, natürlich ohne jeden Anspruch auf Vollständigkeit, nach einer der jetzt diskutierten Methoden geschehen:&lt;br /&gt;
&lt;br /&gt;
* '''Schlüsselsuche''': meint das Suchen von Elementen mit bestimmtem Schlüssel; ein klassisches Beispiel wäre das Suchen in einem Wörterbuch,  die Schlüssel entsprechen hier den Wörtern, die Datensätze wären die zu den Wörtern gehörigen Eintragungen.&lt;br /&gt;
&lt;br /&gt;
* '''Bereichssuche''': Im Allgemeinen meint die Bereichssuche in n-Dimensionen die Selektion von Elementen mit Eigenschaften aus einem bestimmten n-dimensionalen Volumen. Im eindimensionalen Fall will man alle Elemente finden, deren Eigenschaft(en) in einem bestimmten Intervall liegen. Die Verallgemeinerung auf n-Dimensionen ist offensichtlich. Ein Beispiel für die Bereichssuche in einer 3D-Kugel wäre ein Handy mit Geolokalisierung, welches alle Restaurants in einem Umkreis von 500m findet. Lineare Ungleichungen werden graphisch durch [http://de.wikipedia.org/wiki/Hyperebene Hyperebenen] repräsentiert. In 2D sind diese Hyperebenen Geraden. Die Ungleichungen können dann den Lösungsraum in irgendeiner Form begrenzen.&lt;br /&gt;
&lt;br /&gt;
* '''Ähnlichkeitssuche''': Finde Elemente, die gegebenen Eigenschaften möglichst ähnlich sind. Ein prominentes Beispiel ist Google (=Ähnlichkeit zwischen Suchbegriffen und Dokumenten) oder das Suchen des nächstengelegenen Restaurants (Ähnlichkeit zwischen eigener Position und Position des Restaurants). Ein wichtigster Spezialfall ist die ''nächste-nachbar Suche''.&lt;br /&gt;
&lt;br /&gt;
* '''Graphensuche''': Hier wäre beispielsweise das Problem optimaler Wege zu nennen (Navigationssuche). Dieser Punkt wird später im Verlauf der Vorlesung noch einmal aufgegriffen werden.&lt;br /&gt;
&lt;br /&gt;
Im jetzt Folgenden wird nur noch die ''Schlüsselsuche'' betrachtet werden.&lt;br /&gt;
&lt;br /&gt;
==Sequentielle Suche==&lt;br /&gt;
&lt;br /&gt;
Die ''sequentielle'' oder ''lineare'' Suche ist die 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 = sequentialSearch(a, key) &lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq &amp;lt;/math&amp;gt; foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def sequentialSearch(a, key):&lt;br /&gt;
    for i in range(len(a)):&lt;br /&gt;
        if a[i] == key:  # bzw. allgemeiner a[i].key == key &lt;br /&gt;
            return i&lt;br /&gt;
    return -1&lt;br /&gt;
&lt;br /&gt;
Wir wollen jetzt die Komplexität dieses Algorithmus bestimmen, wobei die Problemgröße durch &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt; gegeben ist. &lt;br /&gt;
&lt;br /&gt;
Dabei nimmt man an, dass der innerste Vergleich (a[i] == key) jeweils &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt; ist (diese Annahme könnte verletzt sein, wenn der Vergleichsoperator überladen ist und dadurch eine höhere Komplexität hat). Dieser Vergleich wird in der for-Schleife jeweils N-mal durchgeführt (&amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt;), so dass man nach der Verschachtelungsregel eine gesamte Komplexität von &amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt; erhält.&lt;br /&gt;
&lt;br /&gt;
Der Name ''lineare'' Suche rührt von diesem linearen Anwachsen der Komplexität mit der Arraygröße her.&lt;br /&gt;
&lt;br /&gt;
==Binäre Suche==&lt;br /&gt;
&lt;br /&gt;
Wie wir weiter unten zeigen werden, gestattet es diese Suchmethode, die Gesamtdauer der Suche in großen Datensätzen beträchtlich zu verringern. Die Methode beruht auf dem [http://de.wikipedia.org/wiki/Divide_and_Conquer Divide and Conquer-Prinzip], wobei die Suche in jedem Schritt rekursiv auf eine Hälfte des Datensatzes eingeschränkt wird. Weitere Details zur Methode sind [http://de.wikipedia.org/wiki/Bin%C3%A4re_Suche hier] zu finden. &lt;br /&gt;
&lt;br /&gt;
Die Methode ist nur dann anwendbar beziehungsweise effektiv, wenn folgendes gilt:&lt;br /&gt;
&lt;br /&gt;
# Auf der Eigenschaft der Daten, die zur Suche verwendet wird, ist eine Ordnung im Sinne von &amp;lt; oder &amp;gt; definiert.&lt;br /&gt;
# Wir wollen uns auf Datensätze beschränken, die schon fertig aufgebaut sind, in die also keine neuen Elemente mehr eingefügt werden, wenn man mit dem Suchen beginnt. Ist dies nicht der Fall, so ist unter Umständen die Implementierung über einen [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum Binärbaum] (siehe auch weiter unten) geschickter. &lt;br /&gt;
&lt;br /&gt;
Der folgende Algorithmus zeigt eine beispielhafte Implementierung der Methode (wir setzen wieder &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt;):&lt;br /&gt;
&lt;br /&gt;
 a = [...,...]     # array&lt;br /&gt;
 a.sort()   # sortiere über Ordnung des Schlüssels&lt;br /&gt;
 foundIndex = binSearch(a, key, 0, len(a))  # (Array, Schlüssel, von wo bis wo suchen im Array)&lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq&amp;lt;/math&amp;gt;  foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def binSearch(a, key, start, end):  # start ist 1. Index, end ist letzter Index + 1&lt;br /&gt;
    size = end - start   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if size &amp;lt;= 0:   # Bereich leer?  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return -1   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    center = (start + end)/2   # Integer Division, Ergebnis wird abgerundet, wichtig für ganzzahlige Indizes &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if a[center] == key:  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return center  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    elif a[center] &amp;lt; key:  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return binSearch(a, key, center + 1, end)  # Rekursion&lt;br /&gt;
    else:&lt;br /&gt;
        return binSearch(a, key, start + 1, center)  # Rekursion&lt;br /&gt;
&lt;br /&gt;
Zur Berechnung der Komplexität dieses Algorithmus vernachlässigen wir zunächst den Aufwand, den die Sortierung weiter oben verursacht. Dieser Schritt mag oder mag nicht zulässig sein.&lt;br /&gt;
&lt;br /&gt;
Nach der Sequenzregel haben auch alle &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;.  Es bleibt die Komplexität der Rekursion zu berechnen. Die gesamte Komplexität des Algorithmus (jetzt als Funktion f bezeichnet) setzt sich zusammen aus den oben erwähnten &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen sowie der Rekursion und ist &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f(N) = \mathcal{O}(1) + f(N/2) = \mathcal{O}(1) + \mathcal{O}(1) + f(N/4) = ... = \underbrace{\mathcal{O}(1) + ... + \mathcal{O}(1) + \underbrace{f(0)}_{\mathcal{O}(1)\, \rightarrow \,\mathrm{size-Abfrage}}}_{n+1 \,\mathrm{Terme}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Falls jetzt gilt &amp;lt;math&amp;gt; N = 2^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \rightarrow f(N) = \mathcal{O}(1) \cdot \mathcal{O}(n+1) = \mathcal{O}(n) = \mathcal{O}(\lg N) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Für große Datenmengen ist die ''binäre Suche'' also weit effizienter als die ''lineare Suche''. Verdoppelt sich beispielsweise die zu durchsuchende Datenmenge, so verdoppelt sich der Aufwand für die ''sequentielle Suche'' - bei der ''binären Suche'' hingegen benötigt man lediglich eine zusätzliche Vergleichsoperation. &lt;br /&gt;
&lt;br /&gt;
Für kleine Daten (&amp;lt;math&amp;gt; N = 4,\, 5 &amp;lt;/math&amp;gt;) ist die ''sequentielle Suche'' jedoch schneller als die ''binäre Suche'', da hier die rekursiven Funktionsaufrufe teurer als das Mehr an Vergleichen sind. Ein anderer ungünstiger Fall ist gegeben, wenn nur sehr wenige Suchanfragen erfolgen (weniger als &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; viele). Dann wird der Aufwand durch das Sortieren des Arrays dominiert, ist also &amp;lt;math&amp;gt;\mathcal{O}(N \lg N) &amp;lt;/math&amp;gt;. Auch dann ist sequentielle Suche vorzuziehen.&lt;br /&gt;
&lt;br /&gt;
Eine relativ einfache Möglichkeit, die ''binäre Suche'' zu verbessern, ist die sogenannte ''Interpolationssuche''. Hierbei wird die neue Position für die Suche, also die Mitte des Arrays, durch eine Schätzung ersetzt, die angibt, wo sich der Schlüssel innerhalb des Arrays befinden könnte. Bei der Suche in einem Telefonbuch nach dem Namen Zebra würde man ja auch nicht in der Mitte anfangen. Näheres hierzu im Buch von ''Sedgewick''.&lt;br /&gt;
&lt;br /&gt;
Um sich den Algorithmus der ''binären Suche'' klar zu machen, ist es instruktiv, sich die folgende Tabelle genauer anzusehen, die die sukzessive Belegung der Variablen bei verschiedenen Anfragen beschreibt. Die Testfälle wurden nach dem Prinzip des ''domain partitioning'' gewählt. Das zugehörige Array hat die Einträge&lt;br /&gt;
&lt;br /&gt;
 a = [2, 3, 4, 5, 6]&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align:center&amp;quot; border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;5&amp;quot; cellspacing=&amp;quot;0&amp;quot; &lt;br /&gt;
! gesuchter key   !!  start      !! end  !! size !! center !! return &amp;lt;br/&amp;gt; (-1 oder index)  !! Kommentare  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 4     ||0            || 5    ||  5   || 2   ||  2         || gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 2     || 0           || 5    ||  5   || 2   ||            || linker Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||           &lt;br /&gt;
|-&lt;br /&gt;
|       ||  0          || 1    ||  1   || 0   ||  0         || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 1     ||0            || 5    ||  5   || 2   ||            || links außerhalb&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 1    ||  1   || 0   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 0    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 6     ||0            || 5    ||  5   || 2   ||            || rechter Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    || 2    || 4   || 4          || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 5     ||0            || 5    ||  5   || 2   ||            || typischer Fall&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||3            || 5    ||  2   || 4   ||            ||  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       || 3           || 4    ||  1   || 3   || 3          || gefunden&lt;br /&gt;
|- &lt;br /&gt;
| 7     ||0            || 5    ||  5   || 2   ||            || rechts außerhalb        &lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    ||  2   || 4   ||            ||&lt;br /&gt;
|-&lt;br /&gt;
|       ||5            || 5    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Suche in einem Binärbaum ==&lt;br /&gt;
&lt;br /&gt;
Eine kurze Einführung in Binärbäume findet man [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum hier].&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum.png|text-top|300x300px|Zur Illustration von Bäumen]]&lt;br /&gt;
&lt;br /&gt;
Bäume sind zweidimensional verkettete Strukturen. Sie gehören zu den fundamentalen Datenstrukturen in der Informatik. Da man in Bäumen nicht nur Daten speichern kann, sondern auch relevante Beziehungen der Daten untereinander, festgelegt über eine Ordnung auf der vergleichenden Dateneigenschaft (''Schlüssel''), eignen sich Bäume also insbesondere, um gesuchte Daten schnell wieder auffinden zu können.&lt;br /&gt;
&lt;br /&gt;
Ein ''Binärbaum'' wie oben skizziert besteht aus einer Menge von ''Knoten'', die untereinander durch ''Kanten'' verbunden sind. Jeder Knoten hat einen linken und einen rechten Unterbaum, der auch leer sein kann (in Python ließe sich dies mit ''None'' implementieren). Führt eine Kante von Knoten A zu Knoten B, so heißt A Vater von B und B Kind von A. Es gibt genau einen Knoten ohne Vater, den man ''Wurzel'' nennt. Knoten ohne Kinder heißen ''Blätter''.&lt;br /&gt;
&lt;br /&gt;
Ein ''Suchbaum'' hat zusätzlich die Eigenschaft, dass die Schlüssel jedes Knotens sortiert sind. Alle Schlüssel im linken Unterbaum sind kleiner, alle Schlüssel im rechten Unterbaum sind größer als ihr Vater. Wir wollen hierbei annehmen, dass jeder Schlüssel pro Datensatz nur einmal vorkommt, da sich sonst die &amp;gt;- oder &amp;lt;-Relation nicht mehr strikt erfüllen ließe.&lt;br /&gt;
&lt;br /&gt;
Um in einem Baum suchen zu können, wollen wir von zwei Annahmen ausgehen:&lt;br /&gt;
# Einfügen und Suchen im Baum wechseln sich ab. (Wenn das Suchen erst beginnt, nachdem alle Einfügungen erfolgt sind, wäre ein dynamisches Array mit binärer Suche wie oben wesentlich einfacher.)&lt;br /&gt;
# Der Schlüssel, der die Anordnung bestimmt, kennt eine [http://de.wikipedia.org/wiki/Ordnungsrelation Ordnung] (&amp;lt;-Relation oder &amp;gt;-Relation).&lt;br /&gt;
&lt;br /&gt;
Der folgende Python-Code zeigt beispielhaft, wie man in einem Suchbaum suchen könnte. Der Konstruktor für einen Knoten des Suchbaums ließe sich zum Beispiel so implementieren:&lt;br /&gt;
 &lt;br /&gt;
 class Node:&lt;br /&gt;
     def __init__(self, key):&lt;br /&gt;
         self.key = key&lt;br /&gt;
         self.left = self.right = None&lt;br /&gt;
 &lt;br /&gt;
 root = ...    # Wurzel des Suchbaums&lt;br /&gt;
 nodeFound = treeSearch(root, key)   # None, falls nichts gefunden&lt;br /&gt;
 &lt;br /&gt;
 def treeSearch(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return None&lt;br /&gt;
     elif node.key == key:&lt;br /&gt;
         return node&lt;br /&gt;
     elif key &amp;lt; node.key:&lt;br /&gt;
         return treeSearch(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         return treeSearch(node.right, key)&lt;br /&gt;
&lt;br /&gt;
Daraus resultiert der folgende Suchalgorithmus:&lt;br /&gt;
&lt;br /&gt;
 def treeSort(node,array):     # dynamisches Array als 2. Argument&lt;br /&gt;
     if node is None:     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
         return None:&lt;br /&gt;
     treeSort(node.left, array)     # rekursiv&lt;br /&gt;
     array.append(node.key)     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
     treeSort(node.right, array)     # rekursiv&lt;br /&gt;
&lt;br /&gt;
Komplexität:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 f(N)=\mathcal{O}(1)+f(N_\mathrm{left})+f(N_\mathrm{right})=\mathcal{O}(1)+\mathcal{O}(1)+f(N_\mathrm{leftleft})+f(N_\mathrm{leftright})+\mathcal{O}(1)+f(N_\mathrm{rightleft})&lt;br /&gt;
 +f(N_\mathrm{leftright})=N\ast\mathcal{O}(1)=\mathcal{O}(N)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sortier-Pseudocode:&lt;br /&gt;
&lt;br /&gt;
 Sortieren:&lt;br /&gt;
     (Array) a     # unsortiert&lt;br /&gt;
     (tree) t     # zunächst leer&lt;br /&gt;
 (dynamisches Array) r     # später sortiert&lt;br /&gt;
 for e in a:&lt;br /&gt;
     t = treeInsert(t, e)&lt;br /&gt;
 treeSort(t, r)&lt;br /&gt;
&lt;br /&gt;
== Insert ==&lt;br /&gt;
&lt;br /&gt;
Was passiert wenn der key (Schlüssel) schon vorhanden ist?&lt;br /&gt;
* error (exception)&lt;br /&gt;
* nichts einfügen&lt;br /&gt;
* nichts einfügen aber einen boolean return zurückgeben (einfügen=true, false)&lt;br /&gt;
* nochmals eingefügt (z.B. in der Node Klasse)&lt;br /&gt;
&lt;br /&gt;
Wobei die ersten 3 Punkte zur Mengensemantik gehören und der letzte eine Multimenge ist.&lt;br /&gt;
&lt;br /&gt;
Algorithmus im zweiten Fall (nichts einfügen):&lt;br /&gt;
&lt;br /&gt;
 def treeInsert(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return Node(key)     # Alternative Schreibweise: node = Node(key)&lt;br /&gt;
     if node.key == key:      # und dann:&lt;br /&gt;
         return node          # pass&lt;br /&gt;
     elif key &amp;lt; node.key:     # links im Baum&lt;br /&gt;
         node.left = treeInsert(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         node.right = treeInsert(node.right, key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Remove ==&lt;br /&gt;
Fälle:&lt;br /&gt;
# key (bzw. Knoten der key enthält) ist ein Blatt =&amp;gt; einfach löschen&lt;br /&gt;
# node hat &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; linken Unterbaum oder &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; rechten Unterbaum =&amp;gt; durch Unterbaum ersetzen&lt;br /&gt;
# node hat beide Unterbäume:&lt;br /&gt;
#* Suche Vorgänger: max k &amp;lt; key (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; keys); Vorgänger ist immer ein Fall 1 oder Fall 2&lt;br /&gt;
#*:=&amp;gt; ersetze node durch Vorgänger und entferne Vorgänger&lt;br /&gt;
&lt;br /&gt;
 def treePredecessor(node):     # wird nur bei Fall 3 aufgerufen&lt;br /&gt;
     node = node.left&lt;br /&gt;
     while node.right is not None:&lt;br /&gt;
         node = node.right&lt;br /&gt;
     return node&lt;br /&gt;
 &lt;br /&gt;
 def treeRemove(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return node&lt;br /&gt;
     if key &amp;lt; node.key:&lt;br /&gt;
         node.left = treeRemove(node.left, key)&lt;br /&gt;
     elif key &amp;gt; node.key:&lt;br /&gt;
         node.right = treeRemove(node.right, key)&lt;br /&gt;
     else:&lt;br /&gt;
         if node.left is None and node.right is None:     # Fall 1&lt;br /&gt;
             node = None&lt;br /&gt;
         elif node.left is None:     # Fall 2&lt;br /&gt;
             node = node.right       # +&lt;br /&gt;
         elif node.right is None:    # Fall 2&lt;br /&gt;
             node = node.left&lt;br /&gt;
         else:&lt;br /&gt;
             pred = treePredecessor(node)&lt;br /&gt;
             node.key = pred.key&lt;br /&gt;
             node.left = treeRemove(node.left, pred.key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Komplexitätsanalyse ==&lt;br /&gt;
&lt;br /&gt;
* Pfad (Zwischen node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;): &lt;br /&gt;
**Folge von Knoten (node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt;,...,node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt;), sodass:&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;ki&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;ki+1&amp;lt;/sub&amp;gt; haben eine gemeinsame Kante.&lt;br /&gt;
[[Image:Baum_Pfad.png]]&lt;br /&gt;
(Ein Baum ist ein Graph, indem es zwischen beliebigen Knoten stets genau einen Pfad gibt.)&lt;br /&gt;
&lt;br /&gt;
* Länge eines Pfades: Anzahl der Kanten = Anzahl der Knoten - 1&lt;br /&gt;
* Tiefe eines Knotens: Pfadlänge vom Knoten zur Wurzel des Baumes.&lt;br /&gt;
* Tiefe des Baumes: maximale Tiefe eines Knotens&lt;br /&gt;
* Balance eines Baumes: maximale Tiefe(k) - minimale Tiefe(k) (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; Blätter)&lt;br /&gt;
&lt;br /&gt;
== Ungünstigster Fall von treeSearch ==&lt;br /&gt;
&lt;br /&gt;
Komplexität von treeSearch = Länge des Pfades zum Knoten wo &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; gefunden wird, oder erkannt wird, dass &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; nicht im Baum ist.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; wird nicht gefunden, aber für diese Entscheidung muss der Längste Pfad vollständig durchlaufen werden.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(T)&amp;lt;/math&amp;gt; wo T = Tiefe des Baumes&lt;br /&gt;
*: [1,2,3,4,5]:&amp;lt;br /&amp;gt; [[Image:Balance.png]]&lt;br /&gt;
*: Tiefe T = 4, Balance = 4&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; wo N = Anzahl der Elemente.&lt;br /&gt;
&lt;br /&gt;
== Aufgabe ==&lt;br /&gt;
&lt;br /&gt;
Minimiere Balance (erzeuge balancierten Baum):&lt;br /&gt;
# Einfügen in geschickter Reihenfolge (siehe Übungsaufgabe)&lt;br /&gt;
# Selbstbalancierter Baum:&lt;br /&gt;
#* Überprüfen der Balance nach jedem Einfügen&lt;br /&gt;
#* Umstrukturieren des Baumes, falls Balance &amp;gt; 1 (Suchbaum-Bedingung muss erhalten bleiben)&lt;br /&gt;
#* AVL-Bäume (älteste Variante)&lt;br /&gt;
#* Rot-Schwarz-Bäume (verbreiteste Variante)&lt;br /&gt;
#* Treaps (flexibelste Variante, siehe Übung)&lt;br /&gt;
#* Splay trees&lt;br /&gt;
#* Andersson Trees (einfachste Variante)&lt;br /&gt;
(#* Skip Lists (schnellste Variante, aber kein Binärbaum))&lt;br /&gt;
&lt;br /&gt;
== Umstrukturieren, so dass Suchbaumbedingung erhalten bleibt: ==&lt;br /&gt;
&lt;br /&gt;
Rotation: elementare Umstrukturierungen&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum_Rotation.png]]&lt;br /&gt;
&lt;br /&gt;
 def rotateRight(node):&lt;br /&gt;
     newRoot = node.left&lt;br /&gt;
     node.left = newRoot.right&lt;br /&gt;
     newRoot.right = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
 def rotateLeft(node):&lt;br /&gt;
     newRoot = node.right&lt;br /&gt;
     node.right = newRoot.left&lt;br /&gt;
     newRoot.left = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
== Balance eines Suchbaumes ==&lt;br /&gt;
&lt;br /&gt;
===Balance eines Baumes zu  definieren:===&lt;br /&gt;
[[Image:Abbildung1.jpg|250px|right]]&lt;br /&gt;
*betrachte &amp;lt;tt&amp;gt;None&amp;lt;/tt&amp;gt; als zusätzlichen Knoten : '''sentinel'''   ([[englische Sprache|engl.]] für ''Wächter'')&lt;br /&gt;
*oder defeniere speziellen sentinel-Knoten&lt;br /&gt;
*RS - Pfades : von &amp;lt;tt&amp;gt;root&amp;lt;/tt&amp;gt; &amp;amp;rarr; ''sentinel''&lt;br /&gt;
*Länge eines Pfades ''P''      '''''|P|'''''&lt;br /&gt;
* &amp;lt;u&amp;gt;die Balance eines Baumes zu defenieren:&amp;lt;/u&amp;gt;&lt;br /&gt;
: '''''B = max P '''''&amp;lt;math&amp;gt; \in &amp;lt;/math&amp;gt; '''''{RS}    |P| - min |P|'''''&lt;br /&gt;
:'''''{RS}'''''  Menge aller RS-Pfade&lt;br /&gt;
;vollständiger Baum:  Balance &amp;lt;math&amp;gt;~=0&amp;lt;/math&amp;gt;&lt;br /&gt;
:Alle Knoten(außer Blättern) haben 2 Kinder.&lt;br /&gt;
;perfect balancierter Baum:  Balance  &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 1&lt;br /&gt;
'''alternative Defenition für perfect balancierte Bäume'''&lt;br /&gt;
:Für jeden Teilbaum gilt es: rechtes und linkes Kind (für jeden Knoten) sind auch wieder perfect balancierte Bäume und ihre Höhe unterscheidet sich höchstens um '''1'''.&lt;br /&gt;
&lt;br /&gt;
===Größe===&lt;br /&gt;
[[Image:baum-v.png|700px|right]]&lt;br /&gt;
====Größe des vollständigen Baumes====&lt;br /&gt;
Ebene K hat 2 &amp;lt;sup&amp;gt;k&amp;lt;/sup&amp;gt; Knoten.&lt;br /&gt;
Falls Tiefe = d , dann N = 2&amp;lt;sup&amp;gt;0&amp;lt;/sup&amp;gt; + 2&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;.....+ 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; = 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1&lt;br /&gt;
&lt;br /&gt;
====Größe des perfect balancierten Baumes====&lt;br /&gt;
Die Tiefe d, kann nicht besser sein als vollständiger Baum&lt;br /&gt;
:'''=&amp;gt;'''N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2 &amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
;schlechteste perfect balancierter Baum : ist ein vollständiger der Tiefe  d - 1 + 1, N &amp;lt;math&amp;gt; \ge&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:'''=&amp;gt;'''  2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1)   &amp;lt;math&amp;gt;~&amp;lt;&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt;)&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
d &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt;  log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; (d + 1)&lt;br /&gt;
&lt;br /&gt;
*Ergibt die Komplexität der Suche im schlechtesten Fall: Anzahl der Vergleiche pro Knoten( = 2 bzw. = 3)&amp;lt;math&amp;gt;\ast&amp;lt;/math&amp;gt;Anzahl der Knoten&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow ~f(N)\le 2d \le 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
*'''perfekt balancierter Baum '''=&amp;gt; AVL-Baum &lt;br /&gt;
*'''balancierter Baum''':&lt;br /&gt;
::&amp;lt;math&amp;gt;\forall  N:d(N)\le c \ast d (N)&amp;lt;/math&amp;gt; und  &amp;lt;math&amp;gt;1 \le c &amp;lt; \mathcal {1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:: d ist die Tiefe von perfekt balancierten Baum&lt;br /&gt;
&lt;br /&gt;
:''Komplexität der Suche'':&amp;lt;math&amp;gt; ~f(N)\le  c\ast 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
:algorithmisch einfacher als perfekt balancierter Baum, aber fast genauso schnell&lt;br /&gt;
&lt;br /&gt;
====Selbst-balancierter Baum====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  def insertTree(node,key):&lt;br /&gt;
    if node is sentinel:       #(None = sentinel)&lt;br /&gt;
        return Node(key)       &lt;br /&gt;
    if node.key == key:&lt;br /&gt;
        return Node&lt;br /&gt;
    if key &amp;lt; node.key:&lt;br /&gt;
        Node = insertTree(node.left, key)&lt;br /&gt;
    else:&lt;br /&gt;
        node.right = insertTree(node.right, key)&lt;br /&gt;
   #optimiere Balance:&lt;br /&gt;
        return node&lt;br /&gt;
&lt;br /&gt;
===Anderson-Bäume===&lt;br /&gt;
&lt;br /&gt;
  class Node:&lt;br /&gt;
    def__init__(self, key):&lt;br /&gt;
        self.key = key&lt;br /&gt;
        self.left = selft.right = None&lt;br /&gt;
        #einfügen:&lt;br /&gt;
        self.level = 1&lt;br /&gt;
&lt;br /&gt;
:''level'' : kodiert Abstand von sentinel&lt;br /&gt;
====Regeln====&lt;br /&gt;
Es gibt vertikale Kanten(parent.level == child.level + 1 ) und horizontale Kanten(parent.level == child.level)&lt;br /&gt;
:*verfeinerter Regel :  ''level kodiert reduzierten Abstand von Setinel''  (d.b. horizontale Kanten werden nicht gezählt)&lt;br /&gt;
::(die nächste zwei Regeln sichern die Balance):&lt;br /&gt;
::*alle RS-Pfade haben die gleiche reduzierte Länge oder ''root'' hat bei allen Pfaden das gleiche Level&lt;br /&gt;
::*kein Pfad hat 2 aufeinanderer folgende horizontale Kanten&lt;br /&gt;
:*nur Kanten zum rechten Kind dürfen horizontal sein&lt;br /&gt;
:*die reduzierte Höhe jedes Blatts ist '''hr=1'''&lt;br /&gt;
&lt;br /&gt;
====Beweis====&lt;br /&gt;
Vereinfachung des Algorithmus&lt;br /&gt;
:'''Satz''':ein Anderson-Baum ist balanciert.&lt;br /&gt;
*1. Sei '''hr'''- die reduzierte Höhe&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; jeder Teilbaum enthält mindestens &amp;lt;math&amp;gt;N\ge 2^{hr} -1 &amp;lt;/math&amp;gt;  Knoten&lt;br /&gt;
&lt;br /&gt;
::a). Blätter : reduzierte Höhe 1 =&amp;gt; &amp;lt;math&amp;gt; N\ge 2^{1} - 1 = 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
::b). inneren Knoten: jeder Unterbaum hat mindestens reduzierte Höhe &amp;lt;math&amp;gt; ~hr - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt;  jeder Unterbaum hat mindestens &amp;lt;math&amp;gt; ~2^{hr} -1 &amp;lt;/math&amp;gt; Knoten&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt;N \ge 2 (2^{hr-1} -1)+1 = 2^w - 2 + 1 = 2^{hr} -1&amp;lt;/math&amp;gt;&lt;br /&gt;
::::alle RS-Pfade heben gleiche Länge&lt;br /&gt;
[[Image:Anderson.png|700px|right]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
*2. Kein Pfad hat 2 aufeinanderfolgende horizontal Kanten&lt;br /&gt;
:::&amp;lt;math&amp;gt;\Rightarrow  d \le 2 hr + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
*3. zusammen:&lt;br /&gt;
:&amp;lt;math&amp;gt;N \ge 2^{\frac {d}{2}} - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;log_{2}{ N}+1\ge log 2^{\frac {d}{2}} - 1 \ge \frac {d}{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; ~d &amp;lt; 2 log_{2}{(N + 1)}&amp;lt;/math&amp;gt;     - balancierter Baum&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
:'''Suchzeit''' : &amp;lt;math&amp;gt; ~ f(N) = \mathcal{O}(log{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Wie erreicht man die Balance===&lt;br /&gt;
====Rotation====&lt;br /&gt;
&amp;lt;br clear=&amp;quot;both&amp;quot; /&amp;gt;&lt;br /&gt;
[[Image:Abbildung4.jpg|text-top|400px|Illustration von Rotation]]&lt;br /&gt;
&amp;lt;br clear=&amp;quot;both&amp;quot; /&amp;gt;&lt;br /&gt;
   def rotateRight (node):&lt;br /&gt;
      root = node.left&lt;br /&gt;
      node.left = node.right&lt;br /&gt;
      root.right = node&lt;br /&gt;
      return root&lt;br /&gt;
   def rotateLeft(node):&lt;br /&gt;
      root = node.right&lt;br /&gt;
      node.right = node.left&lt;br /&gt;
      root.left = node&lt;br /&gt;
      return root&lt;br /&gt;
&lt;br /&gt;
====Optimierung der Balance====&lt;br /&gt;
   &lt;br /&gt;
&lt;br /&gt;
       if node.left is not sentinel and node.level==node.left.level:&lt;br /&gt;
            node &amp;gt; rotateRight(node)&lt;br /&gt;
       if node.right is not sentinel and node.right.right is  not sentinel and node.level==rotate.right.right.level:&lt;br /&gt;
            node = rotateLeft(node)&lt;br /&gt;
            node.level += 1&lt;/div&gt;</summary>
		<author><name>Nata</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1500</id>
		<title>Suchen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1500"/>
				<updated>2008-05-29T00:25:59Z</updated>
		
		<summary type="html">&lt;p&gt;Nata: /* Rotation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Es wäre super wenn jemand ganz kurz schreiben könnte, was am Donnerstag zu den Funktionen treeInsert/Remove/HasKey gemacht wurde, was man ja für den aktuellen Übungszettel braucht. Danke :-)&amp;lt;br /&amp;gt;&lt;br /&gt;
/edit: an eine Funktion &amp;quot;HasKey&amp;quot; kann ich mich aus der Vorlesung nicht erinnern. Wenn die jemand hat, bitte eintragen. - Protokollant&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--Hallo, ich kenne leider deinen Namen nicht, ich habe den Wiki-Eintrag von letztem Donnerstag gemacht.Da das Thema 'Dynamisches Array' noch Thema von letzter Woche war, hab ich die Wiederholung der amortisierten Kosten von diesem Mittwoch noch bei Effizienz eingetragen, du musst es also nicht noch mal in deinem Wiki eintragen. Du kannst dir aber gerne mal anschauen was ich geschrieben habe, vielleicht fallen dir noch ein paar Sachen ein, die man hätte besser machen können. lg, Franziska.--&amp;gt;&lt;br /&gt;
Das Suchen ist eine grundlegende Operation in der Informatik. Viele Probleme in der Informatik können auf Suchaufgaben zurückgeführt werden.&lt;br /&gt;
&lt;br /&gt;
Gemeint ist mit Suchen das Wiederauffinden einer oder mehrerer Datensätze aus einer Menge von früher gespeicherten Datensätzen. Ein paar einleitende Worte zum Suchproblem findet man [http://de.wikipedia.org/wiki/Suche hier].&lt;br /&gt;
&lt;br /&gt;
== Überblick verschiedener Suchmethoden ==&lt;br /&gt;
&lt;br /&gt;
Um sich der Vielseitigkeit von Suchproblemen bewusst zu werden, ist es sinnvoll, sich einen Überblick über verschiedene Suchmethoden zu verschaffen. &lt;br /&gt;
&lt;br /&gt;
Hier sei auch auf einen bereits existierenden Wikipedia-Artikel zu [http://de.wikipedia.org/wiki/Suchverfahren Suchverfahren] verwiesen.&lt;br /&gt;
&lt;br /&gt;
Allen gemeinsam ist die grundlegende Aufgabe, ein Datenelement mit bestimmten Eigenschaften aus einer großen Menge von Datenelementen zu selektieren.&lt;br /&gt;
Dies kann, natürlich ohne jeden Anspruch auf Vollständigkeit, nach einer der jetzt diskutierten Methoden geschehen:&lt;br /&gt;
&lt;br /&gt;
* '''Schlüsselsuche''': meint das Suchen von Elementen mit bestimmtem Schlüssel; ein klassisches Beispiel wäre das Suchen in einem Wörterbuch,  die Schlüssel entsprechen hier den Wörtern, die Datensätze wären die zu den Wörtern gehörigen Eintragungen.&lt;br /&gt;
&lt;br /&gt;
* '''Bereichssuche''': Im Allgemeinen meint die Bereichssuche in n-Dimensionen die Selektion von Elementen mit Eigenschaften aus einem bestimmten n-dimensionalen Volumen. Im eindimensionalen Fall will man alle Elemente finden, deren Eigenschaft(en) in einem bestimmten Intervall liegen. Die Verallgemeinerung auf n-Dimensionen ist offensichtlich. Ein Beispiel für die Bereichssuche in einer 3D-Kugel wäre ein Handy mit Geolokalisierung, welches alle Restaurants in einem Umkreis von 500m findet. Lineare Ungleichungen werden graphisch durch [http://de.wikipedia.org/wiki/Hyperebene Hyperebenen] repräsentiert. In 2D sind diese Hyperebenen Geraden. Die Ungleichungen können dann den Lösungsraum in irgendeiner Form begrenzen.&lt;br /&gt;
&lt;br /&gt;
* '''Ähnlichkeitssuche''': Finde Elemente, die gegebenen Eigenschaften möglichst ähnlich sind. Ein prominentes Beispiel ist Google (=Ähnlichkeit zwischen Suchbegriffen und Dokumenten) oder das Suchen des nächstengelegenen Restaurants (Ähnlichkeit zwischen eigener Position und Position des Restaurants). Ein wichtigster Spezialfall ist die ''nächste-nachbar Suche''.&lt;br /&gt;
&lt;br /&gt;
* '''Graphensuche''': Hier wäre beispielsweise das Problem optimaler Wege zu nennen (Navigationssuche). Dieser Punkt wird später im Verlauf der Vorlesung noch einmal aufgegriffen werden.&lt;br /&gt;
&lt;br /&gt;
Im jetzt Folgenden wird nur noch die ''Schlüsselsuche'' betrachtet werden.&lt;br /&gt;
&lt;br /&gt;
==Sequentielle Suche==&lt;br /&gt;
&lt;br /&gt;
Die ''sequentielle'' oder ''lineare'' Suche ist die 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 = sequentialSearch(a, key) &lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq &amp;lt;/math&amp;gt; foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def sequentialSearch(a, key):&lt;br /&gt;
    for i in range(len(a)):&lt;br /&gt;
        if a[i] == key:  # bzw. allgemeiner a[i].key == key &lt;br /&gt;
            return i&lt;br /&gt;
    return -1&lt;br /&gt;
&lt;br /&gt;
Wir wollen jetzt die Komplexität dieses Algorithmus bestimmen, wobei die Problemgröße durch &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt; gegeben ist. &lt;br /&gt;
&lt;br /&gt;
Dabei nimmt man an, dass der innerste Vergleich (a[i] == key) jeweils &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt; ist (diese Annahme könnte verletzt sein, wenn der Vergleichsoperator überladen ist und dadurch eine höhere Komplexität hat). Dieser Vergleich wird in der for-Schleife jeweils N-mal durchgeführt (&amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt;), so dass man nach der Verschachtelungsregel eine gesamte Komplexität von &amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt; erhält.&lt;br /&gt;
&lt;br /&gt;
Der Name ''lineare'' Suche rührt von diesem linearen Anwachsen der Komplexität mit der Arraygröße her.&lt;br /&gt;
&lt;br /&gt;
==Binäre Suche==&lt;br /&gt;
&lt;br /&gt;
Wie wir weiter unten zeigen werden, gestattet es diese Suchmethode, die Gesamtdauer der Suche in großen Datensätzen beträchtlich zu verringern. Die Methode beruht auf dem [http://de.wikipedia.org/wiki/Divide_and_Conquer Divide and Conquer-Prinzip], wobei die Suche in jedem Schritt rekursiv auf eine Hälfte des Datensatzes eingeschränkt wird. Weitere Details zur Methode sind [http://de.wikipedia.org/wiki/Bin%C3%A4re_Suche hier] zu finden. &lt;br /&gt;
&lt;br /&gt;
Die Methode ist nur dann anwendbar beziehungsweise effektiv, wenn folgendes gilt:&lt;br /&gt;
&lt;br /&gt;
# Auf der Eigenschaft der Daten, die zur Suche verwendet wird, ist eine Ordnung im Sinne von &amp;lt; oder &amp;gt; definiert.&lt;br /&gt;
# Wir wollen uns auf Datensätze beschränken, die schon fertig aufgebaut sind, in die also keine neuen Elemente mehr eingefügt werden, wenn man mit dem Suchen beginnt. Ist dies nicht der Fall, so ist unter Umständen die Implementierung über einen [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum Binärbaum] (siehe auch weiter unten) geschickter. &lt;br /&gt;
&lt;br /&gt;
Der folgende Algorithmus zeigt eine beispielhafte Implementierung der Methode (wir setzen wieder &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt;):&lt;br /&gt;
&lt;br /&gt;
 a = [...,...]     # array&lt;br /&gt;
 a.sort()   # sortiere über Ordnung des Schlüssels&lt;br /&gt;
 foundIndex = binSearch(a, key, 0, len(a))  # (Array, Schlüssel, von wo bis wo suchen im Array)&lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq&amp;lt;/math&amp;gt;  foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def binSearch(a, key, start, end):  # start ist 1. Index, end ist letzter Index + 1&lt;br /&gt;
    size = end - start   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if size &amp;lt;= 0:   # Bereich leer?  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return -1   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    center = (start + end)/2   # Integer Division, Ergebnis wird abgerundet, wichtig für ganzzahlige Indizes &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if a[center] == key:  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return center  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    elif a[center] &amp;lt; key:  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return binSearch(a, key, center + 1, end)  # Rekursion&lt;br /&gt;
    else:&lt;br /&gt;
        return binSearch(a, key, start + 1, center)  # Rekursion&lt;br /&gt;
&lt;br /&gt;
Zur Berechnung der Komplexität dieses Algorithmus vernachlässigen wir zunächst den Aufwand, den die Sortierung weiter oben verursacht. Dieser Schritt mag oder mag nicht zulässig sein.&lt;br /&gt;
&lt;br /&gt;
Nach der Sequenzregel haben auch alle &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;.  Es bleibt die Komplexität der Rekursion zu berechnen. Die gesamte Komplexität des Algorithmus (jetzt als Funktion f bezeichnet) setzt sich zusammen aus den oben erwähnten &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen sowie der Rekursion und ist &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f(N) = \mathcal{O}(1) + f(N/2) = \mathcal{O}(1) + \mathcal{O}(1) + f(N/4) = ... = \underbrace{\mathcal{O}(1) + ... + \mathcal{O}(1) + \underbrace{f(0)}_{\mathcal{O}(1)\, \rightarrow \,\mathrm{size-Abfrage}}}_{n+1 \,\mathrm{Terme}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Falls jetzt gilt &amp;lt;math&amp;gt; N = 2^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \rightarrow f(N) = \mathcal{O}(1) \cdot \mathcal{O}(n+1) = \mathcal{O}(n) = \mathcal{O}(\lg N) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Für große Datenmengen ist die ''binäre Suche'' also weit effizienter als die ''lineare Suche''. Verdoppelt sich beispielsweise die zu durchsuchende Datenmenge, so verdoppelt sich der Aufwand für die ''sequentielle Suche'' - bei der ''binären Suche'' hingegen benötigt man lediglich eine zusätzliche Vergleichsoperation. &lt;br /&gt;
&lt;br /&gt;
Für kleine Daten (&amp;lt;math&amp;gt; N = 4,\, 5 &amp;lt;/math&amp;gt;) ist die ''sequentielle Suche'' jedoch schneller als die ''binäre Suche'', da hier die rekursiven Funktionsaufrufe teurer als das Mehr an Vergleichen sind. Ein anderer ungünstiger Fall ist gegeben, wenn nur sehr wenige Suchanfragen erfolgen (weniger als &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; viele). Dann wird der Aufwand durch das Sortieren des Arrays dominiert, ist also &amp;lt;math&amp;gt;\mathcal{O}(N \lg N) &amp;lt;/math&amp;gt;. Auch dann ist sequentielle Suche vorzuziehen.&lt;br /&gt;
&lt;br /&gt;
Eine relativ einfache Möglichkeit, die ''binäre Suche'' zu verbessern, ist die sogenannte ''Interpolationssuche''. Hierbei wird die neue Position für die Suche, also die Mitte des Arrays, durch eine Schätzung ersetzt, die angibt, wo sich der Schlüssel innerhalb des Arrays befinden könnte. Bei der Suche in einem Telefonbuch nach dem Namen Zebra würde man ja auch nicht in der Mitte anfangen. Näheres hierzu im Buch von ''Sedgewick''.&lt;br /&gt;
&lt;br /&gt;
Um sich den Algorithmus der ''binären Suche'' klar zu machen, ist es instruktiv, sich die folgende Tabelle genauer anzusehen, die die sukzessive Belegung der Variablen bei verschiedenen Anfragen beschreibt. Die Testfälle wurden nach dem Prinzip des ''domain partitioning'' gewählt. Das zugehörige Array hat die Einträge&lt;br /&gt;
&lt;br /&gt;
 a = [2, 3, 4, 5, 6]&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align:center&amp;quot; border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;5&amp;quot; cellspacing=&amp;quot;0&amp;quot; &lt;br /&gt;
! gesuchter key   !!  start      !! end  !! size !! center !! return &amp;lt;br/&amp;gt; (-1 oder index)  !! Kommentare  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 4     ||0            || 5    ||  5   || 2   ||  2         || gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 2     || 0           || 5    ||  5   || 2   ||            || linker Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||           &lt;br /&gt;
|-&lt;br /&gt;
|       ||  0          || 1    ||  1   || 0   ||  0         || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 1     ||0            || 5    ||  5   || 2   ||            || links außerhalb&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 1    ||  1   || 0   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 0    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 6     ||0            || 5    ||  5   || 2   ||            || rechter Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    || 2    || 4   || 4          || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 5     ||0            || 5    ||  5   || 2   ||            || typischer Fall&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||3            || 5    ||  2   || 4   ||            ||  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       || 3           || 4    ||  1   || 3   || 3          || gefunden&lt;br /&gt;
|- &lt;br /&gt;
| 7     ||0            || 5    ||  5   || 2   ||            || rechts außerhalb        &lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    ||  2   || 4   ||            ||&lt;br /&gt;
|-&lt;br /&gt;
|       ||5            || 5    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Suche in einem Binärbaum ==&lt;br /&gt;
&lt;br /&gt;
Eine kurze Einführung in Binärbäume findet man [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum hier].&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum.png|text-top|300x300px|Zur Illustration von Bäumen]]&lt;br /&gt;
&lt;br /&gt;
Bäume sind zweidimensional verkettete Strukturen. Sie gehören zu den fundamentalen Datenstrukturen in der Informatik. Da man in Bäumen nicht nur Daten speichern kann, sondern auch relevante Beziehungen der Daten untereinander, festgelegt über eine Ordnung auf der vergleichenden Dateneigenschaft (''Schlüssel''), eignen sich Bäume also insbesondere, um gesuchte Daten schnell wieder auffinden zu können.&lt;br /&gt;
&lt;br /&gt;
Ein ''Binärbaum'' wie oben skizziert besteht aus einer Menge von ''Knoten'', die untereinander durch ''Kanten'' verbunden sind. Jeder Knoten hat einen linken und einen rechten Unterbaum, der auch leer sein kann (in Python ließe sich dies mit ''None'' implementieren). Führt eine Kante von Knoten A zu Knoten B, so heißt A Vater von B und B Kind von A. Es gibt genau einen Knoten ohne Vater, den man ''Wurzel'' nennt. Knoten ohne Kinder heißen ''Blätter''.&lt;br /&gt;
&lt;br /&gt;
Ein ''Suchbaum'' hat zusätzlich die Eigenschaft, dass die Schlüssel jedes Knotens sortiert sind. Alle Schlüssel im linken Unterbaum sind kleiner, alle Schlüssel im rechten Unterbaum sind größer als ihr Vater. Wir wollen hierbei annehmen, dass jeder Schlüssel pro Datensatz nur einmal vorkommt, da sich sonst die &amp;gt;- oder &amp;lt;-Relation nicht mehr strikt erfüllen ließe.&lt;br /&gt;
&lt;br /&gt;
Um in einem Baum suchen zu können, wollen wir von zwei Annahmen ausgehen:&lt;br /&gt;
# Einfügen und Suchen im Baum wechseln sich ab. (Wenn das Suchen erst beginnt, nachdem alle Einfügungen erfolgt sind, wäre ein dynamisches Array mit binärer Suche wie oben wesentlich einfacher.)&lt;br /&gt;
# Der Schlüssel, der die Anordnung bestimmt, kennt eine [http://de.wikipedia.org/wiki/Ordnungsrelation Ordnung] (&amp;lt;-Relation oder &amp;gt;-Relation).&lt;br /&gt;
&lt;br /&gt;
Der folgende Python-Code zeigt beispielhaft, wie man in einem Suchbaum suchen könnte. Der Konstruktor für einen Knoten des Suchbaums ließe sich zum Beispiel so implementieren:&lt;br /&gt;
 &lt;br /&gt;
 class Node:&lt;br /&gt;
     def __init__(self, key):&lt;br /&gt;
         self.key = key&lt;br /&gt;
         self.left = self.right = None&lt;br /&gt;
 &lt;br /&gt;
 root = ...    # Wurzel des Suchbaums&lt;br /&gt;
 nodeFound = treeSearch(root, key)   # None, falls nichts gefunden&lt;br /&gt;
 &lt;br /&gt;
 def treeSearch(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return None&lt;br /&gt;
     elif node.key == key:&lt;br /&gt;
         return node&lt;br /&gt;
     elif key &amp;lt; node.key:&lt;br /&gt;
         return treeSearch(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         return treeSearch(node.right, key)&lt;br /&gt;
&lt;br /&gt;
Daraus resultiert der folgende Suchalgorithmus:&lt;br /&gt;
&lt;br /&gt;
 def treeSort(node,array):     # dynamisches Array als 2. Argument&lt;br /&gt;
     if node is None:     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
         return None:&lt;br /&gt;
     treeSort(node.left, array)     # rekursiv&lt;br /&gt;
     array.append(node.key)     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
     treeSort(node.right, array)     # rekursiv&lt;br /&gt;
&lt;br /&gt;
Komplexität:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 f(N)=\mathcal{O}(1)+f(N_\mathrm{left})+f(N_\mathrm{right})=\mathcal{O}(1)+\mathcal{O}(1)+f(N_\mathrm{leftleft})+f(N_\mathrm{leftright})+\mathcal{O}(1)+f(N_\mathrm{rightleft})&lt;br /&gt;
 +f(N_\mathrm{leftright})=N\ast\mathcal{O}(1)=\mathcal{O}(N)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sortier-Pseudocode:&lt;br /&gt;
&lt;br /&gt;
 Sortieren:&lt;br /&gt;
     (Array) a     # unsortiert&lt;br /&gt;
     (tree) t     # zunächst leer&lt;br /&gt;
 (dynamisches Array) r     # später sortiert&lt;br /&gt;
 for e in a:&lt;br /&gt;
     t = treeInsert(t, e)&lt;br /&gt;
 treeSort(t, r)&lt;br /&gt;
&lt;br /&gt;
== Insert ==&lt;br /&gt;
&lt;br /&gt;
Was passiert wenn der key (Schlüssel) schon vorhanden ist?&lt;br /&gt;
* error (exception)&lt;br /&gt;
* nichts einfügen&lt;br /&gt;
* nichts einfügen aber einen boolean return zurückgeben (einfügen=true, false)&lt;br /&gt;
* nochmals eingefügt (z.B. in der Node Klasse)&lt;br /&gt;
&lt;br /&gt;
Wobei die ersten 3 Punkte zur Mengensemantik gehören und der letzte eine Multimenge ist.&lt;br /&gt;
&lt;br /&gt;
Algorithmus im zweiten Fall (nichts einfügen):&lt;br /&gt;
&lt;br /&gt;
 def treeInsert(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return Node(key)     # Alternative Schreibweise: node = Node(key)&lt;br /&gt;
     if node.key == key:      # und dann:&lt;br /&gt;
         return node          # pass&lt;br /&gt;
     elif key &amp;lt; node.key:     # links im Baum&lt;br /&gt;
         node.left = treeInsert(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         node.right = treeInsert(node.right, key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Remove ==&lt;br /&gt;
Fälle:&lt;br /&gt;
# key (bzw. Knoten der key enthält) ist ein Blatt =&amp;gt; einfach löschen&lt;br /&gt;
# node hat &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; linken Unterbaum oder &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; rechten Unterbaum =&amp;gt; durch Unterbaum ersetzen&lt;br /&gt;
# node hat beide Unterbäume:&lt;br /&gt;
#* Suche Vorgänger: max k &amp;lt; key (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; keys); Vorgänger ist immer ein Fall 1 oder Fall 2&lt;br /&gt;
#*:=&amp;gt; ersetze node durch Vorgänger und entferne Vorgänger&lt;br /&gt;
&lt;br /&gt;
 def treePredecessor(node):     # wird nur bei Fall 3 aufgerufen&lt;br /&gt;
     node = node.left&lt;br /&gt;
     while node.right is not None:&lt;br /&gt;
         node = node.right&lt;br /&gt;
     return node&lt;br /&gt;
 &lt;br /&gt;
 def treeRemove(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return node&lt;br /&gt;
     if key &amp;lt; node.key:&lt;br /&gt;
         node.left = treeRemove(node.left, key)&lt;br /&gt;
     elif key &amp;gt; node.key:&lt;br /&gt;
         node.right = treeRemove(node.right, key)&lt;br /&gt;
     else:&lt;br /&gt;
         if node.left is None and node.right is None:     # Fall 1&lt;br /&gt;
             node = None&lt;br /&gt;
         elif node.left is None:     # Fall 2&lt;br /&gt;
             node = node.right       # +&lt;br /&gt;
         elif node.right is None:    # Fall 2&lt;br /&gt;
             node = node.left&lt;br /&gt;
         else:&lt;br /&gt;
             pred = treePredecessor(node)&lt;br /&gt;
             node.key = pred.key&lt;br /&gt;
             node.left = treeRemove(node.left, pred.key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Komplexitätsanalyse ==&lt;br /&gt;
&lt;br /&gt;
* Pfad (Zwischen node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;): &lt;br /&gt;
**Folge von Knoten (node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt;,...,node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt;), sodass:&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;ki&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;ki+1&amp;lt;/sub&amp;gt; haben eine gemeinsame Kante.&lt;br /&gt;
[[Image:Baum_Pfad.png]]&lt;br /&gt;
(Ein Baum ist ein Graph, indem es zwischen beliebigen Knoten stets genau einen Pfad gibt.)&lt;br /&gt;
&lt;br /&gt;
* Länge eines Pfades: Anzahl der Kanten = Anzahl der Knoten - 1&lt;br /&gt;
* Tiefe eines Knotens: Pfadlänge vom Knoten zur Wurzel des Baumes.&lt;br /&gt;
* Tiefe des Baumes: maximale Tiefe eines Knotens&lt;br /&gt;
* Balance eines Baumes: maximale Tiefe(k) - minimale Tiefe(k) (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; Blätter)&lt;br /&gt;
&lt;br /&gt;
== Ungünstigster Fall von treeSearch ==&lt;br /&gt;
&lt;br /&gt;
Komplexität von treeSearch = Länge des Pfades zum Knoten wo &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; gefunden wird, oder erkannt wird, dass &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; nicht im Baum ist.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; wird nicht gefunden, aber für diese Entscheidung muss der Längste Pfad vollständig durchlaufen werden.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(T)&amp;lt;/math&amp;gt; wo T = Tiefe des Baumes&lt;br /&gt;
*: [1,2,3,4,5]:&amp;lt;br /&amp;gt; [[Image:Balance.png]]&lt;br /&gt;
*: Tiefe T = 4, Balance = 4&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; wo N = Anzahl der Elemente.&lt;br /&gt;
&lt;br /&gt;
== Aufgabe ==&lt;br /&gt;
&lt;br /&gt;
Minimiere Balance (erzeuge balancierten Baum):&lt;br /&gt;
# Einfügen in geschickter Reihenfolge (siehe Übungsaufgabe)&lt;br /&gt;
# Selbstbalancierter Baum:&lt;br /&gt;
#* Überprüfen der Balance nach jedem Einfügen&lt;br /&gt;
#* Umstrukturieren des Baumes, falls Balance &amp;gt; 1 (Suchbaum-Bedingung muss erhalten bleiben)&lt;br /&gt;
#* AVL-Bäume (älteste Variante)&lt;br /&gt;
#* Rot-Schwarz-Bäume (verbreiteste Variante)&lt;br /&gt;
#* Treaps (flexibelste Variante, siehe Übung)&lt;br /&gt;
#* Splay trees&lt;br /&gt;
#* Andersson Trees (einfachste Variante)&lt;br /&gt;
(#* Skip Lists (schnellste Variante, aber kein Binärbaum))&lt;br /&gt;
&lt;br /&gt;
== Umstrukturieren, so dass Suchbaumbedingung erhalten bleibt: ==&lt;br /&gt;
&lt;br /&gt;
Rotation: elementare Umstrukturierungen&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum_Rotation.png]]&lt;br /&gt;
&lt;br /&gt;
 def rotateRight(node):&lt;br /&gt;
     newRoot = node.left&lt;br /&gt;
     node.left = newRoot.right&lt;br /&gt;
     newRoot.right = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
 def rotateLeft(node):&lt;br /&gt;
     newRoot = node.right&lt;br /&gt;
     node.right = newRoot.left&lt;br /&gt;
     newRoot.left = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
== Balance eines Suchbaumes ==&lt;br /&gt;
&lt;br /&gt;
===Balance eines Baumes zu  definieren:===&lt;br /&gt;
[[Image:Abbildung1.jpg|250px|right]]&lt;br /&gt;
*betrachte &amp;lt;tt&amp;gt;None&amp;lt;/tt&amp;gt; als zusätzlichen Knoten : '''sentinel'''   ([[englische Sprache|engl.]] für ''Wächter'')&lt;br /&gt;
*oder defeniere speziellen sentinel-Knoten&lt;br /&gt;
*RS - Pfades : von &amp;lt;tt&amp;gt;root&amp;lt;/tt&amp;gt; &amp;amp;rarr; ''sentinel''&lt;br /&gt;
*Länge eines Pfades ''P''      '''''|P|'''''&lt;br /&gt;
* &amp;lt;u&amp;gt;die Balance eines Baumes zu defenieren:&amp;lt;/u&amp;gt;&lt;br /&gt;
: '''''B = max P '''''&amp;lt;math&amp;gt; \in &amp;lt;/math&amp;gt; '''''{RS}    |P| - min |P|'''''&lt;br /&gt;
:'''''{RS}'''''  Menge aller RS-Pfade&lt;br /&gt;
;vollständiger Baum:  Balance &amp;lt;math&amp;gt;~=0&amp;lt;/math&amp;gt;&lt;br /&gt;
:Alle Knoten(außer Blättern) haben 2 Kinder.&lt;br /&gt;
;perfect balancierter Baum:  Balance  &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 1&lt;br /&gt;
'''alternative Defenition für perfect balancierte Bäume'''&lt;br /&gt;
:Für jeden Teilbaum gilt es: rechtes und linkes Kind (für jeden Knoten) sind auch wieder perfect balancierte Bäume und ihre Höhe unterscheidet sich höchstens um '''1'''.&lt;br /&gt;
&lt;br /&gt;
===Größe===&lt;br /&gt;
[[Image:baum-v.png|700px|right]]&lt;br /&gt;
====Größe des vollständigen Baumes====&lt;br /&gt;
Ebene K hat 2 &amp;lt;sup&amp;gt;k&amp;lt;/sup&amp;gt; Knoten.&lt;br /&gt;
Falls Tiefe = d , dann N = 2&amp;lt;sup&amp;gt;0&amp;lt;/sup&amp;gt; + 2&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;.....+ 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; = 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1&lt;br /&gt;
&lt;br /&gt;
====Größe des perfect balancierten Baumes====&lt;br /&gt;
Die Tiefe d, kann nicht besser sein als vollständiger Baum&lt;br /&gt;
:'''=&amp;gt;'''N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2 &amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
;schlechteste perfect balancierter Baum : ist ein vollständiger der Tiefe  d - 1 + 1, N &amp;lt;math&amp;gt; \ge&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:'''=&amp;gt;'''  2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1)   &amp;lt;math&amp;gt;~&amp;lt;&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt;)&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
d &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt;  log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; (d + 1)&lt;br /&gt;
&lt;br /&gt;
*Ergibt die Komplexität der Suche im schlechtesten Fall: Anzahl der Vergleiche pro Knoten( = 2 bzw. = 3)&amp;lt;math&amp;gt;\ast&amp;lt;/math&amp;gt;Anzahl der Knoten&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow ~f(N)\le 2d \le 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
*'''perfekt balancierter Baum '''=&amp;gt; AVL-Baum &lt;br /&gt;
*'''balancierter Baum''':&lt;br /&gt;
::&amp;lt;math&amp;gt;\forall  N:d(N)\le c \ast d (N)&amp;lt;/math&amp;gt; und  &amp;lt;math&amp;gt;1 \le c &amp;lt; \mathcal {1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:: d ist die Tiefe von perfekt balancierten Baum&lt;br /&gt;
&lt;br /&gt;
:''Komplexität der Suche'':&amp;lt;math&amp;gt; ~f(N)\le  c\ast 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
:algorithmisch einfacher als perfekt balancierter Baum, aber fast genauso schnell&lt;br /&gt;
&lt;br /&gt;
====Selbst-balancierter Baum====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  def insertTree(node,key):&lt;br /&gt;
    if node is sentinel:       #(None = sentinel)&lt;br /&gt;
        return Node(key)       &lt;br /&gt;
    if node.key == key:&lt;br /&gt;
        return Node&lt;br /&gt;
    if key &amp;lt; node.key:&lt;br /&gt;
        Node = insertTree(node.left, key)&lt;br /&gt;
    else:&lt;br /&gt;
        node.right = insertTree(node.right, key)&lt;br /&gt;
   #optimiere Balance:&lt;br /&gt;
        return node&lt;br /&gt;
&lt;br /&gt;
===Anderson-Bäume===&lt;br /&gt;
&lt;br /&gt;
  class Node:&lt;br /&gt;
    def__init__(self, key):&lt;br /&gt;
        self.key = key&lt;br /&gt;
        self.left = selft.right = None&lt;br /&gt;
        #einfügen:&lt;br /&gt;
        self.level = 1&lt;br /&gt;
&lt;br /&gt;
:''level'' : kodiert Abstand von sentinel&lt;br /&gt;
====Regeln====&lt;br /&gt;
Es gibt vertikale Kanten(parent.level == child.level + 1 ) und horizontale Kanten(parent.level == child.level)&lt;br /&gt;
:*verfeinerter Regel :  ''level kodiert reduzierten Abstand von Setinel''  (d.b. horizontale Kanten werden nicht gezählt)&lt;br /&gt;
::(die nächste zwei Regeln sichern die Balance):&lt;br /&gt;
::*alle RS-Pfade haben die gleiche reduzierte Länge oder ''root'' hat bei allen Pfaden das gleiche Level&lt;br /&gt;
::*kein Pfad hat 2 aufeinanderer folgende horizontale Kanten&lt;br /&gt;
:*nur Kanten zum rechten Kind dürfen horizontal sein&lt;br /&gt;
:*die reduzierte Höhe jedes Blatts ist '''hr=1'''&lt;br /&gt;
&lt;br /&gt;
====Beweis====&lt;br /&gt;
Vereinfachung des Algorithmus&lt;br /&gt;
:'''Satz''':ein Anderson-Baum ist balanciert.&lt;br /&gt;
*1. Sei '''hr'''- die reduzierte Höhe&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; jeder Teilbaum enthält mindestens &amp;lt;math&amp;gt;N\ge 2^{hr} -1 &amp;lt;/math&amp;gt;  Knoten&lt;br /&gt;
&lt;br /&gt;
::a). Blätter : reduzierte Höhe 1 =&amp;gt; &amp;lt;math&amp;gt; N\ge 2^{1} - 1 = 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
::b). inneren Knoten: jeder Unterbaum hat mindestens reduzierte Höhe &amp;lt;math&amp;gt; ~hr - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt;  jeder Unterbaum hat mindestens &amp;lt;math&amp;gt; ~2^{hr} -1 &amp;lt;/math&amp;gt; Knoten&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt;N \ge 2 (2^{hr-1} -1)+1 = 2^w - 2 + 1 = 2^{hr} -1&amp;lt;/math&amp;gt;&lt;br /&gt;
::::alle RS-Pfade heben gleiche Länge&lt;br /&gt;
[[Image:Anderson.png|700px|right]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
*2. Kein Pfad hat 2 aufeinanderfolgende horizontal Kanten&lt;br /&gt;
:::&amp;lt;math&amp;gt;\Rightarrow  d \le 2 hr + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
*3. zusammen:&lt;br /&gt;
:&amp;lt;math&amp;gt;N \ge 2^{\frac {d}{2}} - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;log_{2}{ N}+1\ge log 2^{\frac {d}{2}} - 1 \ge \frac {d}{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; ~d &amp;lt; 2 log_{2}{(N + 1)}&amp;lt;/math&amp;gt;     - balancierter Baum&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
:'''Suchzeit''' : &amp;lt;math&amp;gt; ~ f(N) = \mathcal{O}(log{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Wie erreicht man die Balance===&lt;br /&gt;
====Rotation====&lt;br /&gt;
[[Image:Abbildung4.jpg|text-top|350px|Illustration von Rotation]]&lt;br /&gt;
&amp;lt;br clear=&amp;quot;both&amp;quot; /&amp;gt;&lt;br /&gt;
   def rotateRight (node):&lt;br /&gt;
      root = node.left&lt;br /&gt;
      node.left = node.right&lt;br /&gt;
      root.right = node&lt;br /&gt;
      return root&lt;br /&gt;
   def rotateLeft(node):&lt;br /&gt;
      root = node.right&lt;br /&gt;
      node.right = node.left&lt;br /&gt;
      root.left = node&lt;br /&gt;
      return root&lt;br /&gt;
&lt;br /&gt;
====Optimierung der Balance====&lt;br /&gt;
   &lt;br /&gt;
&lt;br /&gt;
       if node.left is not sentinel and node.level==node.left.level:&lt;br /&gt;
            node &amp;gt; rotateRight(node)&lt;br /&gt;
       if node.right is not sentinel and node.right.right is  not sentinel and node.level==rotate.right.right.level:&lt;br /&gt;
            node = rotateLeft(node)&lt;br /&gt;
            node.level += 1&lt;/div&gt;</summary>
		<author><name>Nata</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1499</id>
		<title>Suchen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1499"/>
				<updated>2008-05-29T00:25:17Z</updated>
		
		<summary type="html">&lt;p&gt;Nata: /* Rotation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Es wäre super wenn jemand ganz kurz schreiben könnte, was am Donnerstag zu den Funktionen treeInsert/Remove/HasKey gemacht wurde, was man ja für den aktuellen Übungszettel braucht. Danke :-)&amp;lt;br /&amp;gt;&lt;br /&gt;
/edit: an eine Funktion &amp;quot;HasKey&amp;quot; kann ich mich aus der Vorlesung nicht erinnern. Wenn die jemand hat, bitte eintragen. - Protokollant&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--Hallo, ich kenne leider deinen Namen nicht, ich habe den Wiki-Eintrag von letztem Donnerstag gemacht.Da das Thema 'Dynamisches Array' noch Thema von letzter Woche war, hab ich die Wiederholung der amortisierten Kosten von diesem Mittwoch noch bei Effizienz eingetragen, du musst es also nicht noch mal in deinem Wiki eintragen. Du kannst dir aber gerne mal anschauen was ich geschrieben habe, vielleicht fallen dir noch ein paar Sachen ein, die man hätte besser machen können. lg, Franziska.--&amp;gt;&lt;br /&gt;
Das Suchen ist eine grundlegende Operation in der Informatik. Viele Probleme in der Informatik können auf Suchaufgaben zurückgeführt werden.&lt;br /&gt;
&lt;br /&gt;
Gemeint ist mit Suchen das Wiederauffinden einer oder mehrerer Datensätze aus einer Menge von früher gespeicherten Datensätzen. Ein paar einleitende Worte zum Suchproblem findet man [http://de.wikipedia.org/wiki/Suche hier].&lt;br /&gt;
&lt;br /&gt;
== Überblick verschiedener Suchmethoden ==&lt;br /&gt;
&lt;br /&gt;
Um sich der Vielseitigkeit von Suchproblemen bewusst zu werden, ist es sinnvoll, sich einen Überblick über verschiedene Suchmethoden zu verschaffen. &lt;br /&gt;
&lt;br /&gt;
Hier sei auch auf einen bereits existierenden Wikipedia-Artikel zu [http://de.wikipedia.org/wiki/Suchverfahren Suchverfahren] verwiesen.&lt;br /&gt;
&lt;br /&gt;
Allen gemeinsam ist die grundlegende Aufgabe, ein Datenelement mit bestimmten Eigenschaften aus einer großen Menge von Datenelementen zu selektieren.&lt;br /&gt;
Dies kann, natürlich ohne jeden Anspruch auf Vollständigkeit, nach einer der jetzt diskutierten Methoden geschehen:&lt;br /&gt;
&lt;br /&gt;
* '''Schlüsselsuche''': meint das Suchen von Elementen mit bestimmtem Schlüssel; ein klassisches Beispiel wäre das Suchen in einem Wörterbuch,  die Schlüssel entsprechen hier den Wörtern, die Datensätze wären die zu den Wörtern gehörigen Eintragungen.&lt;br /&gt;
&lt;br /&gt;
* '''Bereichssuche''': Im Allgemeinen meint die Bereichssuche in n-Dimensionen die Selektion von Elementen mit Eigenschaften aus einem bestimmten n-dimensionalen Volumen. Im eindimensionalen Fall will man alle Elemente finden, deren Eigenschaft(en) in einem bestimmten Intervall liegen. Die Verallgemeinerung auf n-Dimensionen ist offensichtlich. Ein Beispiel für die Bereichssuche in einer 3D-Kugel wäre ein Handy mit Geolokalisierung, welches alle Restaurants in einem Umkreis von 500m findet. Lineare Ungleichungen werden graphisch durch [http://de.wikipedia.org/wiki/Hyperebene Hyperebenen] repräsentiert. In 2D sind diese Hyperebenen Geraden. Die Ungleichungen können dann den Lösungsraum in irgendeiner Form begrenzen.&lt;br /&gt;
&lt;br /&gt;
* '''Ähnlichkeitssuche''': Finde Elemente, die gegebenen Eigenschaften möglichst ähnlich sind. Ein prominentes Beispiel ist Google (=Ähnlichkeit zwischen Suchbegriffen und Dokumenten) oder das Suchen des nächstengelegenen Restaurants (Ähnlichkeit zwischen eigener Position und Position des Restaurants). Ein wichtigster Spezialfall ist die ''nächste-nachbar Suche''.&lt;br /&gt;
&lt;br /&gt;
* '''Graphensuche''': Hier wäre beispielsweise das Problem optimaler Wege zu nennen (Navigationssuche). Dieser Punkt wird später im Verlauf der Vorlesung noch einmal aufgegriffen werden.&lt;br /&gt;
&lt;br /&gt;
Im jetzt Folgenden wird nur noch die ''Schlüsselsuche'' betrachtet werden.&lt;br /&gt;
&lt;br /&gt;
==Sequentielle Suche==&lt;br /&gt;
&lt;br /&gt;
Die ''sequentielle'' oder ''lineare'' Suche ist die 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 = sequentialSearch(a, key) &lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq &amp;lt;/math&amp;gt; foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def sequentialSearch(a, key):&lt;br /&gt;
    for i in range(len(a)):&lt;br /&gt;
        if a[i] == key:  # bzw. allgemeiner a[i].key == key &lt;br /&gt;
            return i&lt;br /&gt;
    return -1&lt;br /&gt;
&lt;br /&gt;
Wir wollen jetzt die Komplexität dieses Algorithmus bestimmen, wobei die Problemgröße durch &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt; gegeben ist. &lt;br /&gt;
&lt;br /&gt;
Dabei nimmt man an, dass der innerste Vergleich (a[i] == key) jeweils &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt; ist (diese Annahme könnte verletzt sein, wenn der Vergleichsoperator überladen ist und dadurch eine höhere Komplexität hat). Dieser Vergleich wird in der for-Schleife jeweils N-mal durchgeführt (&amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt;), so dass man nach der Verschachtelungsregel eine gesamte Komplexität von &amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt; erhält.&lt;br /&gt;
&lt;br /&gt;
Der Name ''lineare'' Suche rührt von diesem linearen Anwachsen der Komplexität mit der Arraygröße her.&lt;br /&gt;
&lt;br /&gt;
==Binäre Suche==&lt;br /&gt;
&lt;br /&gt;
Wie wir weiter unten zeigen werden, gestattet es diese Suchmethode, die Gesamtdauer der Suche in großen Datensätzen beträchtlich zu verringern. Die Methode beruht auf dem [http://de.wikipedia.org/wiki/Divide_and_Conquer Divide and Conquer-Prinzip], wobei die Suche in jedem Schritt rekursiv auf eine Hälfte des Datensatzes eingeschränkt wird. Weitere Details zur Methode sind [http://de.wikipedia.org/wiki/Bin%C3%A4re_Suche hier] zu finden. &lt;br /&gt;
&lt;br /&gt;
Die Methode ist nur dann anwendbar beziehungsweise effektiv, wenn folgendes gilt:&lt;br /&gt;
&lt;br /&gt;
# Auf der Eigenschaft der Daten, die zur Suche verwendet wird, ist eine Ordnung im Sinne von &amp;lt; oder &amp;gt; definiert.&lt;br /&gt;
# Wir wollen uns auf Datensätze beschränken, die schon fertig aufgebaut sind, in die also keine neuen Elemente mehr eingefügt werden, wenn man mit dem Suchen beginnt. Ist dies nicht der Fall, so ist unter Umständen die Implementierung über einen [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum Binärbaum] (siehe auch weiter unten) geschickter. &lt;br /&gt;
&lt;br /&gt;
Der folgende Algorithmus zeigt eine beispielhafte Implementierung der Methode (wir setzen wieder &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt;):&lt;br /&gt;
&lt;br /&gt;
 a = [...,...]     # array&lt;br /&gt;
 a.sort()   # sortiere über Ordnung des Schlüssels&lt;br /&gt;
 foundIndex = binSearch(a, key, 0, len(a))  # (Array, Schlüssel, von wo bis wo suchen im Array)&lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq&amp;lt;/math&amp;gt;  foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def binSearch(a, key, start, end):  # start ist 1. Index, end ist letzter Index + 1&lt;br /&gt;
    size = end - start   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if size &amp;lt;= 0:   # Bereich leer?  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return -1   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    center = (start + end)/2   # Integer Division, Ergebnis wird abgerundet, wichtig für ganzzahlige Indizes &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if a[center] == key:  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return center  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    elif a[center] &amp;lt; key:  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return binSearch(a, key, center + 1, end)  # Rekursion&lt;br /&gt;
    else:&lt;br /&gt;
        return binSearch(a, key, start + 1, center)  # Rekursion&lt;br /&gt;
&lt;br /&gt;
Zur Berechnung der Komplexität dieses Algorithmus vernachlässigen wir zunächst den Aufwand, den die Sortierung weiter oben verursacht. Dieser Schritt mag oder mag nicht zulässig sein.&lt;br /&gt;
&lt;br /&gt;
Nach der Sequenzregel haben auch alle &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;.  Es bleibt die Komplexität der Rekursion zu berechnen. Die gesamte Komplexität des Algorithmus (jetzt als Funktion f bezeichnet) setzt sich zusammen aus den oben erwähnten &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen sowie der Rekursion und ist &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f(N) = \mathcal{O}(1) + f(N/2) = \mathcal{O}(1) + \mathcal{O}(1) + f(N/4) = ... = \underbrace{\mathcal{O}(1) + ... + \mathcal{O}(1) + \underbrace{f(0)}_{\mathcal{O}(1)\, \rightarrow \,\mathrm{size-Abfrage}}}_{n+1 \,\mathrm{Terme}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Falls jetzt gilt &amp;lt;math&amp;gt; N = 2^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \rightarrow f(N) = \mathcal{O}(1) \cdot \mathcal{O}(n+1) = \mathcal{O}(n) = \mathcal{O}(\lg N) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Für große Datenmengen ist die ''binäre Suche'' also weit effizienter als die ''lineare Suche''. Verdoppelt sich beispielsweise die zu durchsuchende Datenmenge, so verdoppelt sich der Aufwand für die ''sequentielle Suche'' - bei der ''binären Suche'' hingegen benötigt man lediglich eine zusätzliche Vergleichsoperation. &lt;br /&gt;
&lt;br /&gt;
Für kleine Daten (&amp;lt;math&amp;gt; N = 4,\, 5 &amp;lt;/math&amp;gt;) ist die ''sequentielle Suche'' jedoch schneller als die ''binäre Suche'', da hier die rekursiven Funktionsaufrufe teurer als das Mehr an Vergleichen sind. Ein anderer ungünstiger Fall ist gegeben, wenn nur sehr wenige Suchanfragen erfolgen (weniger als &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; viele). Dann wird der Aufwand durch das Sortieren des Arrays dominiert, ist also &amp;lt;math&amp;gt;\mathcal{O}(N \lg N) &amp;lt;/math&amp;gt;. Auch dann ist sequentielle Suche vorzuziehen.&lt;br /&gt;
&lt;br /&gt;
Eine relativ einfache Möglichkeit, die ''binäre Suche'' zu verbessern, ist die sogenannte ''Interpolationssuche''. Hierbei wird die neue Position für die Suche, also die Mitte des Arrays, durch eine Schätzung ersetzt, die angibt, wo sich der Schlüssel innerhalb des Arrays befinden könnte. Bei der Suche in einem Telefonbuch nach dem Namen Zebra würde man ja auch nicht in der Mitte anfangen. Näheres hierzu im Buch von ''Sedgewick''.&lt;br /&gt;
&lt;br /&gt;
Um sich den Algorithmus der ''binären Suche'' klar zu machen, ist es instruktiv, sich die folgende Tabelle genauer anzusehen, die die sukzessive Belegung der Variablen bei verschiedenen Anfragen beschreibt. Die Testfälle wurden nach dem Prinzip des ''domain partitioning'' gewählt. Das zugehörige Array hat die Einträge&lt;br /&gt;
&lt;br /&gt;
 a = [2, 3, 4, 5, 6]&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align:center&amp;quot; border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;5&amp;quot; cellspacing=&amp;quot;0&amp;quot; &lt;br /&gt;
! gesuchter key   !!  start      !! end  !! size !! center !! return &amp;lt;br/&amp;gt; (-1 oder index)  !! Kommentare  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 4     ||0            || 5    ||  5   || 2   ||  2         || gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 2     || 0           || 5    ||  5   || 2   ||            || linker Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||           &lt;br /&gt;
|-&lt;br /&gt;
|       ||  0          || 1    ||  1   || 0   ||  0         || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 1     ||0            || 5    ||  5   || 2   ||            || links außerhalb&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 1    ||  1   || 0   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 0    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 6     ||0            || 5    ||  5   || 2   ||            || rechter Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    || 2    || 4   || 4          || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 5     ||0            || 5    ||  5   || 2   ||            || typischer Fall&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||3            || 5    ||  2   || 4   ||            ||  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       || 3           || 4    ||  1   || 3   || 3          || gefunden&lt;br /&gt;
|- &lt;br /&gt;
| 7     ||0            || 5    ||  5   || 2   ||            || rechts außerhalb        &lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    ||  2   || 4   ||            ||&lt;br /&gt;
|-&lt;br /&gt;
|       ||5            || 5    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Suche in einem Binärbaum ==&lt;br /&gt;
&lt;br /&gt;
Eine kurze Einführung in Binärbäume findet man [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum hier].&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum.png|text-top|300x300px|Zur Illustration von Bäumen]]&lt;br /&gt;
&lt;br /&gt;
Bäume sind zweidimensional verkettete Strukturen. Sie gehören zu den fundamentalen Datenstrukturen in der Informatik. Da man in Bäumen nicht nur Daten speichern kann, sondern auch relevante Beziehungen der Daten untereinander, festgelegt über eine Ordnung auf der vergleichenden Dateneigenschaft (''Schlüssel''), eignen sich Bäume also insbesondere, um gesuchte Daten schnell wieder auffinden zu können.&lt;br /&gt;
&lt;br /&gt;
Ein ''Binärbaum'' wie oben skizziert besteht aus einer Menge von ''Knoten'', die untereinander durch ''Kanten'' verbunden sind. Jeder Knoten hat einen linken und einen rechten Unterbaum, der auch leer sein kann (in Python ließe sich dies mit ''None'' implementieren). Führt eine Kante von Knoten A zu Knoten B, so heißt A Vater von B und B Kind von A. Es gibt genau einen Knoten ohne Vater, den man ''Wurzel'' nennt. Knoten ohne Kinder heißen ''Blätter''.&lt;br /&gt;
&lt;br /&gt;
Ein ''Suchbaum'' hat zusätzlich die Eigenschaft, dass die Schlüssel jedes Knotens sortiert sind. Alle Schlüssel im linken Unterbaum sind kleiner, alle Schlüssel im rechten Unterbaum sind größer als ihr Vater. Wir wollen hierbei annehmen, dass jeder Schlüssel pro Datensatz nur einmal vorkommt, da sich sonst die &amp;gt;- oder &amp;lt;-Relation nicht mehr strikt erfüllen ließe.&lt;br /&gt;
&lt;br /&gt;
Um in einem Baum suchen zu können, wollen wir von zwei Annahmen ausgehen:&lt;br /&gt;
# Einfügen und Suchen im Baum wechseln sich ab. (Wenn das Suchen erst beginnt, nachdem alle Einfügungen erfolgt sind, wäre ein dynamisches Array mit binärer Suche wie oben wesentlich einfacher.)&lt;br /&gt;
# Der Schlüssel, der die Anordnung bestimmt, kennt eine [http://de.wikipedia.org/wiki/Ordnungsrelation Ordnung] (&amp;lt;-Relation oder &amp;gt;-Relation).&lt;br /&gt;
&lt;br /&gt;
Der folgende Python-Code zeigt beispielhaft, wie man in einem Suchbaum suchen könnte. Der Konstruktor für einen Knoten des Suchbaums ließe sich zum Beispiel so implementieren:&lt;br /&gt;
 &lt;br /&gt;
 class Node:&lt;br /&gt;
     def __init__(self, key):&lt;br /&gt;
         self.key = key&lt;br /&gt;
         self.left = self.right = None&lt;br /&gt;
 &lt;br /&gt;
 root = ...    # Wurzel des Suchbaums&lt;br /&gt;
 nodeFound = treeSearch(root, key)   # None, falls nichts gefunden&lt;br /&gt;
 &lt;br /&gt;
 def treeSearch(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return None&lt;br /&gt;
     elif node.key == key:&lt;br /&gt;
         return node&lt;br /&gt;
     elif key &amp;lt; node.key:&lt;br /&gt;
         return treeSearch(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         return treeSearch(node.right, key)&lt;br /&gt;
&lt;br /&gt;
Daraus resultiert der folgende Suchalgorithmus:&lt;br /&gt;
&lt;br /&gt;
 def treeSort(node,array):     # dynamisches Array als 2. Argument&lt;br /&gt;
     if node is None:     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
         return None:&lt;br /&gt;
     treeSort(node.left, array)     # rekursiv&lt;br /&gt;
     array.append(node.key)     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
     treeSort(node.right, array)     # rekursiv&lt;br /&gt;
&lt;br /&gt;
Komplexität:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 f(N)=\mathcal{O}(1)+f(N_\mathrm{left})+f(N_\mathrm{right})=\mathcal{O}(1)+\mathcal{O}(1)+f(N_\mathrm{leftleft})+f(N_\mathrm{leftright})+\mathcal{O}(1)+f(N_\mathrm{rightleft})&lt;br /&gt;
 +f(N_\mathrm{leftright})=N\ast\mathcal{O}(1)=\mathcal{O}(N)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sortier-Pseudocode:&lt;br /&gt;
&lt;br /&gt;
 Sortieren:&lt;br /&gt;
     (Array) a     # unsortiert&lt;br /&gt;
     (tree) t     # zunächst leer&lt;br /&gt;
 (dynamisches Array) r     # später sortiert&lt;br /&gt;
 for e in a:&lt;br /&gt;
     t = treeInsert(t, e)&lt;br /&gt;
 treeSort(t, r)&lt;br /&gt;
&lt;br /&gt;
== Insert ==&lt;br /&gt;
&lt;br /&gt;
Was passiert wenn der key (Schlüssel) schon vorhanden ist?&lt;br /&gt;
* error (exception)&lt;br /&gt;
* nichts einfügen&lt;br /&gt;
* nichts einfügen aber einen boolean return zurückgeben (einfügen=true, false)&lt;br /&gt;
* nochmals eingefügt (z.B. in der Node Klasse)&lt;br /&gt;
&lt;br /&gt;
Wobei die ersten 3 Punkte zur Mengensemantik gehören und der letzte eine Multimenge ist.&lt;br /&gt;
&lt;br /&gt;
Algorithmus im zweiten Fall (nichts einfügen):&lt;br /&gt;
&lt;br /&gt;
 def treeInsert(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return Node(key)     # Alternative Schreibweise: node = Node(key)&lt;br /&gt;
     if node.key == key:      # und dann:&lt;br /&gt;
         return node          # pass&lt;br /&gt;
     elif key &amp;lt; node.key:     # links im Baum&lt;br /&gt;
         node.left = treeInsert(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         node.right = treeInsert(node.right, key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Remove ==&lt;br /&gt;
Fälle:&lt;br /&gt;
# key (bzw. Knoten der key enthält) ist ein Blatt =&amp;gt; einfach löschen&lt;br /&gt;
# node hat &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; linken Unterbaum oder &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; rechten Unterbaum =&amp;gt; durch Unterbaum ersetzen&lt;br /&gt;
# node hat beide Unterbäume:&lt;br /&gt;
#* Suche Vorgänger: max k &amp;lt; key (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; keys); Vorgänger ist immer ein Fall 1 oder Fall 2&lt;br /&gt;
#*:=&amp;gt; ersetze node durch Vorgänger und entferne Vorgänger&lt;br /&gt;
&lt;br /&gt;
 def treePredecessor(node):     # wird nur bei Fall 3 aufgerufen&lt;br /&gt;
     node = node.left&lt;br /&gt;
     while node.right is not None:&lt;br /&gt;
         node = node.right&lt;br /&gt;
     return node&lt;br /&gt;
 &lt;br /&gt;
 def treeRemove(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return node&lt;br /&gt;
     if key &amp;lt; node.key:&lt;br /&gt;
         node.left = treeRemove(node.left, key)&lt;br /&gt;
     elif key &amp;gt; node.key:&lt;br /&gt;
         node.right = treeRemove(node.right, key)&lt;br /&gt;
     else:&lt;br /&gt;
         if node.left is None and node.right is None:     # Fall 1&lt;br /&gt;
             node = None&lt;br /&gt;
         elif node.left is None:     # Fall 2&lt;br /&gt;
             node = node.right       # +&lt;br /&gt;
         elif node.right is None:    # Fall 2&lt;br /&gt;
             node = node.left&lt;br /&gt;
         else:&lt;br /&gt;
             pred = treePredecessor(node)&lt;br /&gt;
             node.key = pred.key&lt;br /&gt;
             node.left = treeRemove(node.left, pred.key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Komplexitätsanalyse ==&lt;br /&gt;
&lt;br /&gt;
* Pfad (Zwischen node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;): &lt;br /&gt;
**Folge von Knoten (node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt;,...,node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt;), sodass:&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;ki&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;ki+1&amp;lt;/sub&amp;gt; haben eine gemeinsame Kante.&lt;br /&gt;
[[Image:Baum_Pfad.png]]&lt;br /&gt;
(Ein Baum ist ein Graph, indem es zwischen beliebigen Knoten stets genau einen Pfad gibt.)&lt;br /&gt;
&lt;br /&gt;
* Länge eines Pfades: Anzahl der Kanten = Anzahl der Knoten - 1&lt;br /&gt;
* Tiefe eines Knotens: Pfadlänge vom Knoten zur Wurzel des Baumes.&lt;br /&gt;
* Tiefe des Baumes: maximale Tiefe eines Knotens&lt;br /&gt;
* Balance eines Baumes: maximale Tiefe(k) - minimale Tiefe(k) (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; Blätter)&lt;br /&gt;
&lt;br /&gt;
== Ungünstigster Fall von treeSearch ==&lt;br /&gt;
&lt;br /&gt;
Komplexität von treeSearch = Länge des Pfades zum Knoten wo &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; gefunden wird, oder erkannt wird, dass &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; nicht im Baum ist.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; wird nicht gefunden, aber für diese Entscheidung muss der Längste Pfad vollständig durchlaufen werden.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(T)&amp;lt;/math&amp;gt; wo T = Tiefe des Baumes&lt;br /&gt;
*: [1,2,3,4,5]:&amp;lt;br /&amp;gt; [[Image:Balance.png]]&lt;br /&gt;
*: Tiefe T = 4, Balance = 4&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; wo N = Anzahl der Elemente.&lt;br /&gt;
&lt;br /&gt;
== Aufgabe ==&lt;br /&gt;
&lt;br /&gt;
Minimiere Balance (erzeuge balancierten Baum):&lt;br /&gt;
# Einfügen in geschickter Reihenfolge (siehe Übungsaufgabe)&lt;br /&gt;
# Selbstbalancierter Baum:&lt;br /&gt;
#* Überprüfen der Balance nach jedem Einfügen&lt;br /&gt;
#* Umstrukturieren des Baumes, falls Balance &amp;gt; 1 (Suchbaum-Bedingung muss erhalten bleiben)&lt;br /&gt;
#* AVL-Bäume (älteste Variante)&lt;br /&gt;
#* Rot-Schwarz-Bäume (verbreiteste Variante)&lt;br /&gt;
#* Treaps (flexibelste Variante, siehe Übung)&lt;br /&gt;
#* Splay trees&lt;br /&gt;
#* Andersson Trees (einfachste Variante)&lt;br /&gt;
(#* Skip Lists (schnellste Variante, aber kein Binärbaum))&lt;br /&gt;
&lt;br /&gt;
== Umstrukturieren, so dass Suchbaumbedingung erhalten bleibt: ==&lt;br /&gt;
&lt;br /&gt;
Rotation: elementare Umstrukturierungen&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum_Rotation.png]]&lt;br /&gt;
&lt;br /&gt;
 def rotateRight(node):&lt;br /&gt;
     newRoot = node.left&lt;br /&gt;
     node.left = newRoot.right&lt;br /&gt;
     newRoot.right = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
 def rotateLeft(node):&lt;br /&gt;
     newRoot = node.right&lt;br /&gt;
     node.right = newRoot.left&lt;br /&gt;
     newRoot.left = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
== Balance eines Suchbaumes ==&lt;br /&gt;
&lt;br /&gt;
===Balance eines Baumes zu  definieren:===&lt;br /&gt;
[[Image:Abbildung1.jpg|250px|right]]&lt;br /&gt;
*betrachte &amp;lt;tt&amp;gt;None&amp;lt;/tt&amp;gt; als zusätzlichen Knoten : '''sentinel'''   ([[englische Sprache|engl.]] für ''Wächter'')&lt;br /&gt;
*oder defeniere speziellen sentinel-Knoten&lt;br /&gt;
*RS - Pfades : von &amp;lt;tt&amp;gt;root&amp;lt;/tt&amp;gt; &amp;amp;rarr; ''sentinel''&lt;br /&gt;
*Länge eines Pfades ''P''      '''''|P|'''''&lt;br /&gt;
* &amp;lt;u&amp;gt;die Balance eines Baumes zu defenieren:&amp;lt;/u&amp;gt;&lt;br /&gt;
: '''''B = max P '''''&amp;lt;math&amp;gt; \in &amp;lt;/math&amp;gt; '''''{RS}    |P| - min |P|'''''&lt;br /&gt;
:'''''{RS}'''''  Menge aller RS-Pfade&lt;br /&gt;
;vollständiger Baum:  Balance &amp;lt;math&amp;gt;~=0&amp;lt;/math&amp;gt;&lt;br /&gt;
:Alle Knoten(außer Blättern) haben 2 Kinder.&lt;br /&gt;
;perfect balancierter Baum:  Balance  &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 1&lt;br /&gt;
'''alternative Defenition für perfect balancierte Bäume'''&lt;br /&gt;
:Für jeden Teilbaum gilt es: rechtes und linkes Kind (für jeden Knoten) sind auch wieder perfect balancierte Bäume und ihre Höhe unterscheidet sich höchstens um '''1'''.&lt;br /&gt;
&lt;br /&gt;
===Größe===&lt;br /&gt;
[[Image:baum-v.png|700px|right]]&lt;br /&gt;
====Größe des vollständigen Baumes====&lt;br /&gt;
Ebene K hat 2 &amp;lt;sup&amp;gt;k&amp;lt;/sup&amp;gt; Knoten.&lt;br /&gt;
Falls Tiefe = d , dann N = 2&amp;lt;sup&amp;gt;0&amp;lt;/sup&amp;gt; + 2&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;.....+ 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; = 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1&lt;br /&gt;
&lt;br /&gt;
====Größe des perfect balancierten Baumes====&lt;br /&gt;
Die Tiefe d, kann nicht besser sein als vollständiger Baum&lt;br /&gt;
:'''=&amp;gt;'''N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2 &amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
;schlechteste perfect balancierter Baum : ist ein vollständiger der Tiefe  d - 1 + 1, N &amp;lt;math&amp;gt; \ge&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:'''=&amp;gt;'''  2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1)   &amp;lt;math&amp;gt;~&amp;lt;&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt;)&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
d &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt;  log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; (d + 1)&lt;br /&gt;
&lt;br /&gt;
*Ergibt die Komplexität der Suche im schlechtesten Fall: Anzahl der Vergleiche pro Knoten( = 2 bzw. = 3)&amp;lt;math&amp;gt;\ast&amp;lt;/math&amp;gt;Anzahl der Knoten&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow ~f(N)\le 2d \le 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
*'''perfekt balancierter Baum '''=&amp;gt; AVL-Baum &lt;br /&gt;
*'''balancierter Baum''':&lt;br /&gt;
::&amp;lt;math&amp;gt;\forall  N:d(N)\le c \ast d (N)&amp;lt;/math&amp;gt; und  &amp;lt;math&amp;gt;1 \le c &amp;lt; \mathcal {1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:: d ist die Tiefe von perfekt balancierten Baum&lt;br /&gt;
&lt;br /&gt;
:''Komplexität der Suche'':&amp;lt;math&amp;gt; ~f(N)\le  c\ast 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
:algorithmisch einfacher als perfekt balancierter Baum, aber fast genauso schnell&lt;br /&gt;
&lt;br /&gt;
====Selbst-balancierter Baum====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  def insertTree(node,key):&lt;br /&gt;
    if node is sentinel:       #(None = sentinel)&lt;br /&gt;
        return Node(key)       &lt;br /&gt;
    if node.key == key:&lt;br /&gt;
        return Node&lt;br /&gt;
    if key &amp;lt; node.key:&lt;br /&gt;
        Node = insertTree(node.left, key)&lt;br /&gt;
    else:&lt;br /&gt;
        node.right = insertTree(node.right, key)&lt;br /&gt;
   #optimiere Balance:&lt;br /&gt;
        return node&lt;br /&gt;
&lt;br /&gt;
===Anderson-Bäume===&lt;br /&gt;
&lt;br /&gt;
  class Node:&lt;br /&gt;
    def__init__(self, key):&lt;br /&gt;
        self.key = key&lt;br /&gt;
        self.left = selft.right = None&lt;br /&gt;
        #einfügen:&lt;br /&gt;
        self.level = 1&lt;br /&gt;
&lt;br /&gt;
:''level'' : kodiert Abstand von sentinel&lt;br /&gt;
====Regeln====&lt;br /&gt;
Es gibt vertikale Kanten(parent.level == child.level + 1 ) und horizontale Kanten(parent.level == child.level)&lt;br /&gt;
:*verfeinerter Regel :  ''level kodiert reduzierten Abstand von Setinel''  (d.b. horizontale Kanten werden nicht gezählt)&lt;br /&gt;
::(die nächste zwei Regeln sichern die Balance):&lt;br /&gt;
::*alle RS-Pfade haben die gleiche reduzierte Länge oder ''root'' hat bei allen Pfaden das gleiche Level&lt;br /&gt;
::*kein Pfad hat 2 aufeinanderer folgende horizontale Kanten&lt;br /&gt;
:*nur Kanten zum rechten Kind dürfen horizontal sein&lt;br /&gt;
:*die reduzierte Höhe jedes Blatts ist '''hr=1'''&lt;br /&gt;
&lt;br /&gt;
====Beweis====&lt;br /&gt;
Vereinfachung des Algorithmus&lt;br /&gt;
:'''Satz''':ein Anderson-Baum ist balanciert.&lt;br /&gt;
*1. Sei '''hr'''- die reduzierte Höhe&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; jeder Teilbaum enthält mindestens &amp;lt;math&amp;gt;N\ge 2^{hr} -1 &amp;lt;/math&amp;gt;  Knoten&lt;br /&gt;
&lt;br /&gt;
::a). Blätter : reduzierte Höhe 1 =&amp;gt; &amp;lt;math&amp;gt; N\ge 2^{1} - 1 = 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
::b). inneren Knoten: jeder Unterbaum hat mindestens reduzierte Höhe &amp;lt;math&amp;gt; ~hr - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt;  jeder Unterbaum hat mindestens &amp;lt;math&amp;gt; ~2^{hr} -1 &amp;lt;/math&amp;gt; Knoten&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt;N \ge 2 (2^{hr-1} -1)+1 = 2^w - 2 + 1 = 2^{hr} -1&amp;lt;/math&amp;gt;&lt;br /&gt;
::::alle RS-Pfade heben gleiche Länge&lt;br /&gt;
[[Image:Anderson.png|700px|right]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
*2. Kein Pfad hat 2 aufeinanderfolgende horizontal Kanten&lt;br /&gt;
:::&amp;lt;math&amp;gt;\Rightarrow  d \le 2 hr + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
*3. zusammen:&lt;br /&gt;
:&amp;lt;math&amp;gt;N \ge 2^{\frac {d}{2}} - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;log_{2}{ N}+1\ge log 2^{\frac {d}{2}} - 1 \ge \frac {d}{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; ~d &amp;lt; 2 log_{2}{(N + 1)}&amp;lt;/math&amp;gt;     - balancierter Baum&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
:'''Suchzeit''' : &amp;lt;math&amp;gt; ~ f(N) = \mathcal{O}(log{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Wie erreicht man die Balance===&lt;br /&gt;
====Rotation====&lt;br /&gt;
[[Image:Abbildung4.jpg|text-top|350px|Illustration von Rotation]]&amp;lt;br clear=&amp;quot;both&amp;quot; /&amp;gt;&lt;br /&gt;
   def rotateRight (node):&lt;br /&gt;
      root = node.left&lt;br /&gt;
      node.left = node.right&lt;br /&gt;
      root.right = node&lt;br /&gt;
      return root&lt;br /&gt;
   def rotateLeft(node):&lt;br /&gt;
      root = node.right&lt;br /&gt;
      node.right = node.left&lt;br /&gt;
      root.left = node&lt;br /&gt;
      return root&lt;br /&gt;
&lt;br /&gt;
====Optimierung der Balance====&lt;br /&gt;
   &lt;br /&gt;
&lt;br /&gt;
       if node.left is not sentinel and node.level==node.left.level:&lt;br /&gt;
            node &amp;gt; rotateRight(node)&lt;br /&gt;
       if node.right is not sentinel and node.right.right is  not sentinel and node.level==rotate.right.right.level:&lt;br /&gt;
            node = rotateLeft(node)&lt;br /&gt;
            node.level += 1&lt;/div&gt;</summary>
		<author><name>Nata</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1498</id>
		<title>Suchen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1498"/>
				<updated>2008-05-29T00:25:03Z</updated>
		
		<summary type="html">&lt;p&gt;Nata: /* Rotation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Es wäre super wenn jemand ganz kurz schreiben könnte, was am Donnerstag zu den Funktionen treeInsert/Remove/HasKey gemacht wurde, was man ja für den aktuellen Übungszettel braucht. Danke :-)&amp;lt;br /&amp;gt;&lt;br /&gt;
/edit: an eine Funktion &amp;quot;HasKey&amp;quot; kann ich mich aus der Vorlesung nicht erinnern. Wenn die jemand hat, bitte eintragen. - Protokollant&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--Hallo, ich kenne leider deinen Namen nicht, ich habe den Wiki-Eintrag von letztem Donnerstag gemacht.Da das Thema 'Dynamisches Array' noch Thema von letzter Woche war, hab ich die Wiederholung der amortisierten Kosten von diesem Mittwoch noch bei Effizienz eingetragen, du musst es also nicht noch mal in deinem Wiki eintragen. Du kannst dir aber gerne mal anschauen was ich geschrieben habe, vielleicht fallen dir noch ein paar Sachen ein, die man hätte besser machen können. lg, Franziska.--&amp;gt;&lt;br /&gt;
Das Suchen ist eine grundlegende Operation in der Informatik. Viele Probleme in der Informatik können auf Suchaufgaben zurückgeführt werden.&lt;br /&gt;
&lt;br /&gt;
Gemeint ist mit Suchen das Wiederauffinden einer oder mehrerer Datensätze aus einer Menge von früher gespeicherten Datensätzen. Ein paar einleitende Worte zum Suchproblem findet man [http://de.wikipedia.org/wiki/Suche hier].&lt;br /&gt;
&lt;br /&gt;
== Überblick verschiedener Suchmethoden ==&lt;br /&gt;
&lt;br /&gt;
Um sich der Vielseitigkeit von Suchproblemen bewusst zu werden, ist es sinnvoll, sich einen Überblick über verschiedene Suchmethoden zu verschaffen. &lt;br /&gt;
&lt;br /&gt;
Hier sei auch auf einen bereits existierenden Wikipedia-Artikel zu [http://de.wikipedia.org/wiki/Suchverfahren Suchverfahren] verwiesen.&lt;br /&gt;
&lt;br /&gt;
Allen gemeinsam ist die grundlegende Aufgabe, ein Datenelement mit bestimmten Eigenschaften aus einer großen Menge von Datenelementen zu selektieren.&lt;br /&gt;
Dies kann, natürlich ohne jeden Anspruch auf Vollständigkeit, nach einer der jetzt diskutierten Methoden geschehen:&lt;br /&gt;
&lt;br /&gt;
* '''Schlüsselsuche''': meint das Suchen von Elementen mit bestimmtem Schlüssel; ein klassisches Beispiel wäre das Suchen in einem Wörterbuch,  die Schlüssel entsprechen hier den Wörtern, die Datensätze wären die zu den Wörtern gehörigen Eintragungen.&lt;br /&gt;
&lt;br /&gt;
* '''Bereichssuche''': Im Allgemeinen meint die Bereichssuche in n-Dimensionen die Selektion von Elementen mit Eigenschaften aus einem bestimmten n-dimensionalen Volumen. Im eindimensionalen Fall will man alle Elemente finden, deren Eigenschaft(en) in einem bestimmten Intervall liegen. Die Verallgemeinerung auf n-Dimensionen ist offensichtlich. Ein Beispiel für die Bereichssuche in einer 3D-Kugel wäre ein Handy mit Geolokalisierung, welches alle Restaurants in einem Umkreis von 500m findet. Lineare Ungleichungen werden graphisch durch [http://de.wikipedia.org/wiki/Hyperebene Hyperebenen] repräsentiert. In 2D sind diese Hyperebenen Geraden. Die Ungleichungen können dann den Lösungsraum in irgendeiner Form begrenzen.&lt;br /&gt;
&lt;br /&gt;
* '''Ähnlichkeitssuche''': Finde Elemente, die gegebenen Eigenschaften möglichst ähnlich sind. Ein prominentes Beispiel ist Google (=Ähnlichkeit zwischen Suchbegriffen und Dokumenten) oder das Suchen des nächstengelegenen Restaurants (Ähnlichkeit zwischen eigener Position und Position des Restaurants). Ein wichtigster Spezialfall ist die ''nächste-nachbar Suche''.&lt;br /&gt;
&lt;br /&gt;
* '''Graphensuche''': Hier wäre beispielsweise das Problem optimaler Wege zu nennen (Navigationssuche). Dieser Punkt wird später im Verlauf der Vorlesung noch einmal aufgegriffen werden.&lt;br /&gt;
&lt;br /&gt;
Im jetzt Folgenden wird nur noch die ''Schlüsselsuche'' betrachtet werden.&lt;br /&gt;
&lt;br /&gt;
==Sequentielle Suche==&lt;br /&gt;
&lt;br /&gt;
Die ''sequentielle'' oder ''lineare'' Suche ist die 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 = sequentialSearch(a, key) &lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq &amp;lt;/math&amp;gt; foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def sequentialSearch(a, key):&lt;br /&gt;
    for i in range(len(a)):&lt;br /&gt;
        if a[i] == key:  # bzw. allgemeiner a[i].key == key &lt;br /&gt;
            return i&lt;br /&gt;
    return -1&lt;br /&gt;
&lt;br /&gt;
Wir wollen jetzt die Komplexität dieses Algorithmus bestimmen, wobei die Problemgröße durch &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt; gegeben ist. &lt;br /&gt;
&lt;br /&gt;
Dabei nimmt man an, dass der innerste Vergleich (a[i] == key) jeweils &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt; ist (diese Annahme könnte verletzt sein, wenn der Vergleichsoperator überladen ist und dadurch eine höhere Komplexität hat). Dieser Vergleich wird in der for-Schleife jeweils N-mal durchgeführt (&amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt;), so dass man nach der Verschachtelungsregel eine gesamte Komplexität von &amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt; erhält.&lt;br /&gt;
&lt;br /&gt;
Der Name ''lineare'' Suche rührt von diesem linearen Anwachsen der Komplexität mit der Arraygröße her.&lt;br /&gt;
&lt;br /&gt;
==Binäre Suche==&lt;br /&gt;
&lt;br /&gt;
Wie wir weiter unten zeigen werden, gestattet es diese Suchmethode, die Gesamtdauer der Suche in großen Datensätzen beträchtlich zu verringern. Die Methode beruht auf dem [http://de.wikipedia.org/wiki/Divide_and_Conquer Divide and Conquer-Prinzip], wobei die Suche in jedem Schritt rekursiv auf eine Hälfte des Datensatzes eingeschränkt wird. Weitere Details zur Methode sind [http://de.wikipedia.org/wiki/Bin%C3%A4re_Suche hier] zu finden. &lt;br /&gt;
&lt;br /&gt;
Die Methode ist nur dann anwendbar beziehungsweise effektiv, wenn folgendes gilt:&lt;br /&gt;
&lt;br /&gt;
# Auf der Eigenschaft der Daten, die zur Suche verwendet wird, ist eine Ordnung im Sinne von &amp;lt; oder &amp;gt; definiert.&lt;br /&gt;
# Wir wollen uns auf Datensätze beschränken, die schon fertig aufgebaut sind, in die also keine neuen Elemente mehr eingefügt werden, wenn man mit dem Suchen beginnt. Ist dies nicht der Fall, so ist unter Umständen die Implementierung über einen [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum Binärbaum] (siehe auch weiter unten) geschickter. &lt;br /&gt;
&lt;br /&gt;
Der folgende Algorithmus zeigt eine beispielhafte Implementierung der Methode (wir setzen wieder &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt;):&lt;br /&gt;
&lt;br /&gt;
 a = [...,...]     # array&lt;br /&gt;
 a.sort()   # sortiere über Ordnung des Schlüssels&lt;br /&gt;
 foundIndex = binSearch(a, key, 0, len(a))  # (Array, Schlüssel, von wo bis wo suchen im Array)&lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq&amp;lt;/math&amp;gt;  foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def binSearch(a, key, start, end):  # start ist 1. Index, end ist letzter Index + 1&lt;br /&gt;
    size = end - start   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if size &amp;lt;= 0:   # Bereich leer?  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return -1   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    center = (start + end)/2   # Integer Division, Ergebnis wird abgerundet, wichtig für ganzzahlige Indizes &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if a[center] == key:  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return center  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    elif a[center] &amp;lt; key:  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return binSearch(a, key, center + 1, end)  # Rekursion&lt;br /&gt;
    else:&lt;br /&gt;
        return binSearch(a, key, start + 1, center)  # Rekursion&lt;br /&gt;
&lt;br /&gt;
Zur Berechnung der Komplexität dieses Algorithmus vernachlässigen wir zunächst den Aufwand, den die Sortierung weiter oben verursacht. Dieser Schritt mag oder mag nicht zulässig sein.&lt;br /&gt;
&lt;br /&gt;
Nach der Sequenzregel haben auch alle &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;.  Es bleibt die Komplexität der Rekursion zu berechnen. Die gesamte Komplexität des Algorithmus (jetzt als Funktion f bezeichnet) setzt sich zusammen aus den oben erwähnten &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen sowie der Rekursion und ist &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f(N) = \mathcal{O}(1) + f(N/2) = \mathcal{O}(1) + \mathcal{O}(1) + f(N/4) = ... = \underbrace{\mathcal{O}(1) + ... + \mathcal{O}(1) + \underbrace{f(0)}_{\mathcal{O}(1)\, \rightarrow \,\mathrm{size-Abfrage}}}_{n+1 \,\mathrm{Terme}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Falls jetzt gilt &amp;lt;math&amp;gt; N = 2^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \rightarrow f(N) = \mathcal{O}(1) \cdot \mathcal{O}(n+1) = \mathcal{O}(n) = \mathcal{O}(\lg N) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Für große Datenmengen ist die ''binäre Suche'' also weit effizienter als die ''lineare Suche''. Verdoppelt sich beispielsweise die zu durchsuchende Datenmenge, so verdoppelt sich der Aufwand für die ''sequentielle Suche'' - bei der ''binären Suche'' hingegen benötigt man lediglich eine zusätzliche Vergleichsoperation. &lt;br /&gt;
&lt;br /&gt;
Für kleine Daten (&amp;lt;math&amp;gt; N = 4,\, 5 &amp;lt;/math&amp;gt;) ist die ''sequentielle Suche'' jedoch schneller als die ''binäre Suche'', da hier die rekursiven Funktionsaufrufe teurer als das Mehr an Vergleichen sind. Ein anderer ungünstiger Fall ist gegeben, wenn nur sehr wenige Suchanfragen erfolgen (weniger als &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; viele). Dann wird der Aufwand durch das Sortieren des Arrays dominiert, ist also &amp;lt;math&amp;gt;\mathcal{O}(N \lg N) &amp;lt;/math&amp;gt;. Auch dann ist sequentielle Suche vorzuziehen.&lt;br /&gt;
&lt;br /&gt;
Eine relativ einfache Möglichkeit, die ''binäre Suche'' zu verbessern, ist die sogenannte ''Interpolationssuche''. Hierbei wird die neue Position für die Suche, also die Mitte des Arrays, durch eine Schätzung ersetzt, die angibt, wo sich der Schlüssel innerhalb des Arrays befinden könnte. Bei der Suche in einem Telefonbuch nach dem Namen Zebra würde man ja auch nicht in der Mitte anfangen. Näheres hierzu im Buch von ''Sedgewick''.&lt;br /&gt;
&lt;br /&gt;
Um sich den Algorithmus der ''binären Suche'' klar zu machen, ist es instruktiv, sich die folgende Tabelle genauer anzusehen, die die sukzessive Belegung der Variablen bei verschiedenen Anfragen beschreibt. Die Testfälle wurden nach dem Prinzip des ''domain partitioning'' gewählt. Das zugehörige Array hat die Einträge&lt;br /&gt;
&lt;br /&gt;
 a = [2, 3, 4, 5, 6]&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align:center&amp;quot; border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;5&amp;quot; cellspacing=&amp;quot;0&amp;quot; &lt;br /&gt;
! gesuchter key   !!  start      !! end  !! size !! center !! return &amp;lt;br/&amp;gt; (-1 oder index)  !! Kommentare  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 4     ||0            || 5    ||  5   || 2   ||  2         || gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 2     || 0           || 5    ||  5   || 2   ||            || linker Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||           &lt;br /&gt;
|-&lt;br /&gt;
|       ||  0          || 1    ||  1   || 0   ||  0         || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 1     ||0            || 5    ||  5   || 2   ||            || links außerhalb&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 1    ||  1   || 0   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 0    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 6     ||0            || 5    ||  5   || 2   ||            || rechter Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    || 2    || 4   || 4          || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 5     ||0            || 5    ||  5   || 2   ||            || typischer Fall&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||3            || 5    ||  2   || 4   ||            ||  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       || 3           || 4    ||  1   || 3   || 3          || gefunden&lt;br /&gt;
|- &lt;br /&gt;
| 7     ||0            || 5    ||  5   || 2   ||            || rechts außerhalb        &lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    ||  2   || 4   ||            ||&lt;br /&gt;
|-&lt;br /&gt;
|       ||5            || 5    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Suche in einem Binärbaum ==&lt;br /&gt;
&lt;br /&gt;
Eine kurze Einführung in Binärbäume findet man [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum hier].&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum.png|text-top|300x300px|Zur Illustration von Bäumen]]&lt;br /&gt;
&lt;br /&gt;
Bäume sind zweidimensional verkettete Strukturen. Sie gehören zu den fundamentalen Datenstrukturen in der Informatik. Da man in Bäumen nicht nur Daten speichern kann, sondern auch relevante Beziehungen der Daten untereinander, festgelegt über eine Ordnung auf der vergleichenden Dateneigenschaft (''Schlüssel''), eignen sich Bäume also insbesondere, um gesuchte Daten schnell wieder auffinden zu können.&lt;br /&gt;
&lt;br /&gt;
Ein ''Binärbaum'' wie oben skizziert besteht aus einer Menge von ''Knoten'', die untereinander durch ''Kanten'' verbunden sind. Jeder Knoten hat einen linken und einen rechten Unterbaum, der auch leer sein kann (in Python ließe sich dies mit ''None'' implementieren). Führt eine Kante von Knoten A zu Knoten B, so heißt A Vater von B und B Kind von A. Es gibt genau einen Knoten ohne Vater, den man ''Wurzel'' nennt. Knoten ohne Kinder heißen ''Blätter''.&lt;br /&gt;
&lt;br /&gt;
Ein ''Suchbaum'' hat zusätzlich die Eigenschaft, dass die Schlüssel jedes Knotens sortiert sind. Alle Schlüssel im linken Unterbaum sind kleiner, alle Schlüssel im rechten Unterbaum sind größer als ihr Vater. Wir wollen hierbei annehmen, dass jeder Schlüssel pro Datensatz nur einmal vorkommt, da sich sonst die &amp;gt;- oder &amp;lt;-Relation nicht mehr strikt erfüllen ließe.&lt;br /&gt;
&lt;br /&gt;
Um in einem Baum suchen zu können, wollen wir von zwei Annahmen ausgehen:&lt;br /&gt;
# Einfügen und Suchen im Baum wechseln sich ab. (Wenn das Suchen erst beginnt, nachdem alle Einfügungen erfolgt sind, wäre ein dynamisches Array mit binärer Suche wie oben wesentlich einfacher.)&lt;br /&gt;
# Der Schlüssel, der die Anordnung bestimmt, kennt eine [http://de.wikipedia.org/wiki/Ordnungsrelation Ordnung] (&amp;lt;-Relation oder &amp;gt;-Relation).&lt;br /&gt;
&lt;br /&gt;
Der folgende Python-Code zeigt beispielhaft, wie man in einem Suchbaum suchen könnte. Der Konstruktor für einen Knoten des Suchbaums ließe sich zum Beispiel so implementieren:&lt;br /&gt;
 &lt;br /&gt;
 class Node:&lt;br /&gt;
     def __init__(self, key):&lt;br /&gt;
         self.key = key&lt;br /&gt;
         self.left = self.right = None&lt;br /&gt;
 &lt;br /&gt;
 root = ...    # Wurzel des Suchbaums&lt;br /&gt;
 nodeFound = treeSearch(root, key)   # None, falls nichts gefunden&lt;br /&gt;
 &lt;br /&gt;
 def treeSearch(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return None&lt;br /&gt;
     elif node.key == key:&lt;br /&gt;
         return node&lt;br /&gt;
     elif key &amp;lt; node.key:&lt;br /&gt;
         return treeSearch(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         return treeSearch(node.right, key)&lt;br /&gt;
&lt;br /&gt;
Daraus resultiert der folgende Suchalgorithmus:&lt;br /&gt;
&lt;br /&gt;
 def treeSort(node,array):     # dynamisches Array als 2. Argument&lt;br /&gt;
     if node is None:     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
         return None:&lt;br /&gt;
     treeSort(node.left, array)     # rekursiv&lt;br /&gt;
     array.append(node.key)     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
     treeSort(node.right, array)     # rekursiv&lt;br /&gt;
&lt;br /&gt;
Komplexität:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 f(N)=\mathcal{O}(1)+f(N_\mathrm{left})+f(N_\mathrm{right})=\mathcal{O}(1)+\mathcal{O}(1)+f(N_\mathrm{leftleft})+f(N_\mathrm{leftright})+\mathcal{O}(1)+f(N_\mathrm{rightleft})&lt;br /&gt;
 +f(N_\mathrm{leftright})=N\ast\mathcal{O}(1)=\mathcal{O}(N)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sortier-Pseudocode:&lt;br /&gt;
&lt;br /&gt;
 Sortieren:&lt;br /&gt;
     (Array) a     # unsortiert&lt;br /&gt;
     (tree) t     # zunächst leer&lt;br /&gt;
 (dynamisches Array) r     # später sortiert&lt;br /&gt;
 for e in a:&lt;br /&gt;
     t = treeInsert(t, e)&lt;br /&gt;
 treeSort(t, r)&lt;br /&gt;
&lt;br /&gt;
== Insert ==&lt;br /&gt;
&lt;br /&gt;
Was passiert wenn der key (Schlüssel) schon vorhanden ist?&lt;br /&gt;
* error (exception)&lt;br /&gt;
* nichts einfügen&lt;br /&gt;
* nichts einfügen aber einen boolean return zurückgeben (einfügen=true, false)&lt;br /&gt;
* nochmals eingefügt (z.B. in der Node Klasse)&lt;br /&gt;
&lt;br /&gt;
Wobei die ersten 3 Punkte zur Mengensemantik gehören und der letzte eine Multimenge ist.&lt;br /&gt;
&lt;br /&gt;
Algorithmus im zweiten Fall (nichts einfügen):&lt;br /&gt;
&lt;br /&gt;
 def treeInsert(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return Node(key)     # Alternative Schreibweise: node = Node(key)&lt;br /&gt;
     if node.key == key:      # und dann:&lt;br /&gt;
         return node          # pass&lt;br /&gt;
     elif key &amp;lt; node.key:     # links im Baum&lt;br /&gt;
         node.left = treeInsert(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         node.right = treeInsert(node.right, key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Remove ==&lt;br /&gt;
Fälle:&lt;br /&gt;
# key (bzw. Knoten der key enthält) ist ein Blatt =&amp;gt; einfach löschen&lt;br /&gt;
# node hat &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; linken Unterbaum oder &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; rechten Unterbaum =&amp;gt; durch Unterbaum ersetzen&lt;br /&gt;
# node hat beide Unterbäume:&lt;br /&gt;
#* Suche Vorgänger: max k &amp;lt; key (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; keys); Vorgänger ist immer ein Fall 1 oder Fall 2&lt;br /&gt;
#*:=&amp;gt; ersetze node durch Vorgänger und entferne Vorgänger&lt;br /&gt;
&lt;br /&gt;
 def treePredecessor(node):     # wird nur bei Fall 3 aufgerufen&lt;br /&gt;
     node = node.left&lt;br /&gt;
     while node.right is not None:&lt;br /&gt;
         node = node.right&lt;br /&gt;
     return node&lt;br /&gt;
 &lt;br /&gt;
 def treeRemove(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return node&lt;br /&gt;
     if key &amp;lt; node.key:&lt;br /&gt;
         node.left = treeRemove(node.left, key)&lt;br /&gt;
     elif key &amp;gt; node.key:&lt;br /&gt;
         node.right = treeRemove(node.right, key)&lt;br /&gt;
     else:&lt;br /&gt;
         if node.left is None and node.right is None:     # Fall 1&lt;br /&gt;
             node = None&lt;br /&gt;
         elif node.left is None:     # Fall 2&lt;br /&gt;
             node = node.right       # +&lt;br /&gt;
         elif node.right is None:    # Fall 2&lt;br /&gt;
             node = node.left&lt;br /&gt;
         else:&lt;br /&gt;
             pred = treePredecessor(node)&lt;br /&gt;
             node.key = pred.key&lt;br /&gt;
             node.left = treeRemove(node.left, pred.key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Komplexitätsanalyse ==&lt;br /&gt;
&lt;br /&gt;
* Pfad (Zwischen node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;): &lt;br /&gt;
**Folge von Knoten (node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt;,...,node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt;), sodass:&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;ki&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;ki+1&amp;lt;/sub&amp;gt; haben eine gemeinsame Kante.&lt;br /&gt;
[[Image:Baum_Pfad.png]]&lt;br /&gt;
(Ein Baum ist ein Graph, indem es zwischen beliebigen Knoten stets genau einen Pfad gibt.)&lt;br /&gt;
&lt;br /&gt;
* Länge eines Pfades: Anzahl der Kanten = Anzahl der Knoten - 1&lt;br /&gt;
* Tiefe eines Knotens: Pfadlänge vom Knoten zur Wurzel des Baumes.&lt;br /&gt;
* Tiefe des Baumes: maximale Tiefe eines Knotens&lt;br /&gt;
* Balance eines Baumes: maximale Tiefe(k) - minimale Tiefe(k) (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; Blätter)&lt;br /&gt;
&lt;br /&gt;
== Ungünstigster Fall von treeSearch ==&lt;br /&gt;
&lt;br /&gt;
Komplexität von treeSearch = Länge des Pfades zum Knoten wo &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; gefunden wird, oder erkannt wird, dass &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; nicht im Baum ist.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; wird nicht gefunden, aber für diese Entscheidung muss der Längste Pfad vollständig durchlaufen werden.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(T)&amp;lt;/math&amp;gt; wo T = Tiefe des Baumes&lt;br /&gt;
*: [1,2,3,4,5]:&amp;lt;br /&amp;gt; [[Image:Balance.png]]&lt;br /&gt;
*: Tiefe T = 4, Balance = 4&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; wo N = Anzahl der Elemente.&lt;br /&gt;
&lt;br /&gt;
== Aufgabe ==&lt;br /&gt;
&lt;br /&gt;
Minimiere Balance (erzeuge balancierten Baum):&lt;br /&gt;
# Einfügen in geschickter Reihenfolge (siehe Übungsaufgabe)&lt;br /&gt;
# Selbstbalancierter Baum:&lt;br /&gt;
#* Überprüfen der Balance nach jedem Einfügen&lt;br /&gt;
#* Umstrukturieren des Baumes, falls Balance &amp;gt; 1 (Suchbaum-Bedingung muss erhalten bleiben)&lt;br /&gt;
#* AVL-Bäume (älteste Variante)&lt;br /&gt;
#* Rot-Schwarz-Bäume (verbreiteste Variante)&lt;br /&gt;
#* Treaps (flexibelste Variante, siehe Übung)&lt;br /&gt;
#* Splay trees&lt;br /&gt;
#* Andersson Trees (einfachste Variante)&lt;br /&gt;
(#* Skip Lists (schnellste Variante, aber kein Binärbaum))&lt;br /&gt;
&lt;br /&gt;
== Umstrukturieren, so dass Suchbaumbedingung erhalten bleibt: ==&lt;br /&gt;
&lt;br /&gt;
Rotation: elementare Umstrukturierungen&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum_Rotation.png]]&lt;br /&gt;
&lt;br /&gt;
 def rotateRight(node):&lt;br /&gt;
     newRoot = node.left&lt;br /&gt;
     node.left = newRoot.right&lt;br /&gt;
     newRoot.right = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
 def rotateLeft(node):&lt;br /&gt;
     newRoot = node.right&lt;br /&gt;
     node.right = newRoot.left&lt;br /&gt;
     newRoot.left = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
== Balance eines Suchbaumes ==&lt;br /&gt;
&lt;br /&gt;
===Balance eines Baumes zu  definieren:===&lt;br /&gt;
[[Image:Abbildung1.jpg|250px|right]]&lt;br /&gt;
*betrachte &amp;lt;tt&amp;gt;None&amp;lt;/tt&amp;gt; als zusätzlichen Knoten : '''sentinel'''   ([[englische Sprache|engl.]] für ''Wächter'')&lt;br /&gt;
*oder defeniere speziellen sentinel-Knoten&lt;br /&gt;
*RS - Pfades : von &amp;lt;tt&amp;gt;root&amp;lt;/tt&amp;gt; &amp;amp;rarr; ''sentinel''&lt;br /&gt;
*Länge eines Pfades ''P''      '''''|P|'''''&lt;br /&gt;
* &amp;lt;u&amp;gt;die Balance eines Baumes zu defenieren:&amp;lt;/u&amp;gt;&lt;br /&gt;
: '''''B = max P '''''&amp;lt;math&amp;gt; \in &amp;lt;/math&amp;gt; '''''{RS}    |P| - min |P|'''''&lt;br /&gt;
:'''''{RS}'''''  Menge aller RS-Pfade&lt;br /&gt;
;vollständiger Baum:  Balance &amp;lt;math&amp;gt;~=0&amp;lt;/math&amp;gt;&lt;br /&gt;
:Alle Knoten(außer Blättern) haben 2 Kinder.&lt;br /&gt;
;perfect balancierter Baum:  Balance  &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 1&lt;br /&gt;
'''alternative Defenition für perfect balancierte Bäume'''&lt;br /&gt;
:Für jeden Teilbaum gilt es: rechtes und linkes Kind (für jeden Knoten) sind auch wieder perfect balancierte Bäume und ihre Höhe unterscheidet sich höchstens um '''1'''.&lt;br /&gt;
&lt;br /&gt;
===Größe===&lt;br /&gt;
[[Image:baum-v.png|700px|right]]&lt;br /&gt;
====Größe des vollständigen Baumes====&lt;br /&gt;
Ebene K hat 2 &amp;lt;sup&amp;gt;k&amp;lt;/sup&amp;gt; Knoten.&lt;br /&gt;
Falls Tiefe = d , dann N = 2&amp;lt;sup&amp;gt;0&amp;lt;/sup&amp;gt; + 2&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;.....+ 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; = 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1&lt;br /&gt;
&lt;br /&gt;
====Größe des perfect balancierten Baumes====&lt;br /&gt;
Die Tiefe d, kann nicht besser sein als vollständiger Baum&lt;br /&gt;
:'''=&amp;gt;'''N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2 &amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
;schlechteste perfect balancierter Baum : ist ein vollständiger der Tiefe  d - 1 + 1, N &amp;lt;math&amp;gt; \ge&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:'''=&amp;gt;'''  2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1)   &amp;lt;math&amp;gt;~&amp;lt;&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt;)&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
d &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt;  log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; (d + 1)&lt;br /&gt;
&lt;br /&gt;
*Ergibt die Komplexität der Suche im schlechtesten Fall: Anzahl der Vergleiche pro Knoten( = 2 bzw. = 3)&amp;lt;math&amp;gt;\ast&amp;lt;/math&amp;gt;Anzahl der Knoten&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow ~f(N)\le 2d \le 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
*'''perfekt balancierter Baum '''=&amp;gt; AVL-Baum &lt;br /&gt;
*'''balancierter Baum''':&lt;br /&gt;
::&amp;lt;math&amp;gt;\forall  N:d(N)\le c \ast d (N)&amp;lt;/math&amp;gt; und  &amp;lt;math&amp;gt;1 \le c &amp;lt; \mathcal {1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:: d ist die Tiefe von perfekt balancierten Baum&lt;br /&gt;
&lt;br /&gt;
:''Komplexität der Suche'':&amp;lt;math&amp;gt; ~f(N)\le  c\ast 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
:algorithmisch einfacher als perfekt balancierter Baum, aber fast genauso schnell&lt;br /&gt;
&lt;br /&gt;
====Selbst-balancierter Baum====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  def insertTree(node,key):&lt;br /&gt;
    if node is sentinel:       #(None = sentinel)&lt;br /&gt;
        return Node(key)       &lt;br /&gt;
    if node.key == key:&lt;br /&gt;
        return Node&lt;br /&gt;
    if key &amp;lt; node.key:&lt;br /&gt;
        Node = insertTree(node.left, key)&lt;br /&gt;
    else:&lt;br /&gt;
        node.right = insertTree(node.right, key)&lt;br /&gt;
   #optimiere Balance:&lt;br /&gt;
        return node&lt;br /&gt;
&lt;br /&gt;
===Anderson-Bäume===&lt;br /&gt;
&lt;br /&gt;
  class Node:&lt;br /&gt;
    def__init__(self, key):&lt;br /&gt;
        self.key = key&lt;br /&gt;
        self.left = selft.right = None&lt;br /&gt;
        #einfügen:&lt;br /&gt;
        self.level = 1&lt;br /&gt;
&lt;br /&gt;
:''level'' : kodiert Abstand von sentinel&lt;br /&gt;
====Regeln====&lt;br /&gt;
Es gibt vertikale Kanten(parent.level == child.level + 1 ) und horizontale Kanten(parent.level == child.level)&lt;br /&gt;
:*verfeinerter Regel :  ''level kodiert reduzierten Abstand von Setinel''  (d.b. horizontale Kanten werden nicht gezählt)&lt;br /&gt;
::(die nächste zwei Regeln sichern die Balance):&lt;br /&gt;
::*alle RS-Pfade haben die gleiche reduzierte Länge oder ''root'' hat bei allen Pfaden das gleiche Level&lt;br /&gt;
::*kein Pfad hat 2 aufeinanderer folgende horizontale Kanten&lt;br /&gt;
:*nur Kanten zum rechten Kind dürfen horizontal sein&lt;br /&gt;
:*die reduzierte Höhe jedes Blatts ist '''hr=1'''&lt;br /&gt;
&lt;br /&gt;
====Beweis====&lt;br /&gt;
Vereinfachung des Algorithmus&lt;br /&gt;
:'''Satz''':ein Anderson-Baum ist balanciert.&lt;br /&gt;
*1. Sei '''hr'''- die reduzierte Höhe&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; jeder Teilbaum enthält mindestens &amp;lt;math&amp;gt;N\ge 2^{hr} -1 &amp;lt;/math&amp;gt;  Knoten&lt;br /&gt;
&lt;br /&gt;
::a). Blätter : reduzierte Höhe 1 =&amp;gt; &amp;lt;math&amp;gt; N\ge 2^{1} - 1 = 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
::b). inneren Knoten: jeder Unterbaum hat mindestens reduzierte Höhe &amp;lt;math&amp;gt; ~hr - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt;  jeder Unterbaum hat mindestens &amp;lt;math&amp;gt; ~2^{hr} -1 &amp;lt;/math&amp;gt; Knoten&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt;N \ge 2 (2^{hr-1} -1)+1 = 2^w - 2 + 1 = 2^{hr} -1&amp;lt;/math&amp;gt;&lt;br /&gt;
::::alle RS-Pfade heben gleiche Länge&lt;br /&gt;
[[Image:Anderson.png|700px|right]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
*2. Kein Pfad hat 2 aufeinanderfolgende horizontal Kanten&lt;br /&gt;
:::&amp;lt;math&amp;gt;\Rightarrow  d \le 2 hr + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
*3. zusammen:&lt;br /&gt;
:&amp;lt;math&amp;gt;N \ge 2^{\frac {d}{2}} - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;log_{2}{ N}+1\ge log 2^{\frac {d}{2}} - 1 \ge \frac {d}{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; ~d &amp;lt; 2 log_{2}{(N + 1)}&amp;lt;/math&amp;gt;     - balancierter Baum&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
:'''Suchzeit''' : &amp;lt;math&amp;gt; ~ f(N) = \mathcal{O}(log{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Wie erreicht man die Balance===&lt;br /&gt;
====Rotation====&lt;br /&gt;
[[Image:Abbildung4.jpg|text-top|350px|Illustration von Rotation]]&amp;lt;br clear=&amp;quot;both&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
   def rotateRight (node):&lt;br /&gt;
      root = node.left&lt;br /&gt;
      node.left = node.right&lt;br /&gt;
      root.right = node&lt;br /&gt;
      return root&lt;br /&gt;
   def rotateLeft(node):&lt;br /&gt;
      root = node.right&lt;br /&gt;
      node.right = node.left&lt;br /&gt;
      root.left = node&lt;br /&gt;
      return root&lt;br /&gt;
&lt;br /&gt;
====Optimierung der Balance====&lt;br /&gt;
   &lt;br /&gt;
&lt;br /&gt;
       if node.left is not sentinel and node.level==node.left.level:&lt;br /&gt;
            node &amp;gt; rotateRight(node)&lt;br /&gt;
       if node.right is not sentinel and node.right.right is  not sentinel and node.level==rotate.right.right.level:&lt;br /&gt;
            node = rotateLeft(node)&lt;br /&gt;
            node.level += 1&lt;/div&gt;</summary>
		<author><name>Nata</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1497</id>
		<title>Suchen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1497"/>
				<updated>2008-05-29T00:24:44Z</updated>
		
		<summary type="html">&lt;p&gt;Nata: /* Rotation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Es wäre super wenn jemand ganz kurz schreiben könnte, was am Donnerstag zu den Funktionen treeInsert/Remove/HasKey gemacht wurde, was man ja für den aktuellen Übungszettel braucht. Danke :-)&amp;lt;br /&amp;gt;&lt;br /&gt;
/edit: an eine Funktion &amp;quot;HasKey&amp;quot; kann ich mich aus der Vorlesung nicht erinnern. Wenn die jemand hat, bitte eintragen. - Protokollant&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--Hallo, ich kenne leider deinen Namen nicht, ich habe den Wiki-Eintrag von letztem Donnerstag gemacht.Da das Thema 'Dynamisches Array' noch Thema von letzter Woche war, hab ich die Wiederholung der amortisierten Kosten von diesem Mittwoch noch bei Effizienz eingetragen, du musst es also nicht noch mal in deinem Wiki eintragen. Du kannst dir aber gerne mal anschauen was ich geschrieben habe, vielleicht fallen dir noch ein paar Sachen ein, die man hätte besser machen können. lg, Franziska.--&amp;gt;&lt;br /&gt;
Das Suchen ist eine grundlegende Operation in der Informatik. Viele Probleme in der Informatik können auf Suchaufgaben zurückgeführt werden.&lt;br /&gt;
&lt;br /&gt;
Gemeint ist mit Suchen das Wiederauffinden einer oder mehrerer Datensätze aus einer Menge von früher gespeicherten Datensätzen. Ein paar einleitende Worte zum Suchproblem findet man [http://de.wikipedia.org/wiki/Suche hier].&lt;br /&gt;
&lt;br /&gt;
== Überblick verschiedener Suchmethoden ==&lt;br /&gt;
&lt;br /&gt;
Um sich der Vielseitigkeit von Suchproblemen bewusst zu werden, ist es sinnvoll, sich einen Überblick über verschiedene Suchmethoden zu verschaffen. &lt;br /&gt;
&lt;br /&gt;
Hier sei auch auf einen bereits existierenden Wikipedia-Artikel zu [http://de.wikipedia.org/wiki/Suchverfahren Suchverfahren] verwiesen.&lt;br /&gt;
&lt;br /&gt;
Allen gemeinsam ist die grundlegende Aufgabe, ein Datenelement mit bestimmten Eigenschaften aus einer großen Menge von Datenelementen zu selektieren.&lt;br /&gt;
Dies kann, natürlich ohne jeden Anspruch auf Vollständigkeit, nach einer der jetzt diskutierten Methoden geschehen:&lt;br /&gt;
&lt;br /&gt;
* '''Schlüsselsuche''': meint das Suchen von Elementen mit bestimmtem Schlüssel; ein klassisches Beispiel wäre das Suchen in einem Wörterbuch,  die Schlüssel entsprechen hier den Wörtern, die Datensätze wären die zu den Wörtern gehörigen Eintragungen.&lt;br /&gt;
&lt;br /&gt;
* '''Bereichssuche''': Im Allgemeinen meint die Bereichssuche in n-Dimensionen die Selektion von Elementen mit Eigenschaften aus einem bestimmten n-dimensionalen Volumen. Im eindimensionalen Fall will man alle Elemente finden, deren Eigenschaft(en) in einem bestimmten Intervall liegen. Die Verallgemeinerung auf n-Dimensionen ist offensichtlich. Ein Beispiel für die Bereichssuche in einer 3D-Kugel wäre ein Handy mit Geolokalisierung, welches alle Restaurants in einem Umkreis von 500m findet. Lineare Ungleichungen werden graphisch durch [http://de.wikipedia.org/wiki/Hyperebene Hyperebenen] repräsentiert. In 2D sind diese Hyperebenen Geraden. Die Ungleichungen können dann den Lösungsraum in irgendeiner Form begrenzen.&lt;br /&gt;
&lt;br /&gt;
* '''Ähnlichkeitssuche''': Finde Elemente, die gegebenen Eigenschaften möglichst ähnlich sind. Ein prominentes Beispiel ist Google (=Ähnlichkeit zwischen Suchbegriffen und Dokumenten) oder das Suchen des nächstengelegenen Restaurants (Ähnlichkeit zwischen eigener Position und Position des Restaurants). Ein wichtigster Spezialfall ist die ''nächste-nachbar Suche''.&lt;br /&gt;
&lt;br /&gt;
* '''Graphensuche''': Hier wäre beispielsweise das Problem optimaler Wege zu nennen (Navigationssuche). Dieser Punkt wird später im Verlauf der Vorlesung noch einmal aufgegriffen werden.&lt;br /&gt;
&lt;br /&gt;
Im jetzt Folgenden wird nur noch die ''Schlüsselsuche'' betrachtet werden.&lt;br /&gt;
&lt;br /&gt;
==Sequentielle Suche==&lt;br /&gt;
&lt;br /&gt;
Die ''sequentielle'' oder ''lineare'' Suche ist die 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 = sequentialSearch(a, key) &lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq &amp;lt;/math&amp;gt; foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def sequentialSearch(a, key):&lt;br /&gt;
    for i in range(len(a)):&lt;br /&gt;
        if a[i] == key:  # bzw. allgemeiner a[i].key == key &lt;br /&gt;
            return i&lt;br /&gt;
    return -1&lt;br /&gt;
&lt;br /&gt;
Wir wollen jetzt die Komplexität dieses Algorithmus bestimmen, wobei die Problemgröße durch &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt; gegeben ist. &lt;br /&gt;
&lt;br /&gt;
Dabei nimmt man an, dass der innerste Vergleich (a[i] == key) jeweils &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt; ist (diese Annahme könnte verletzt sein, wenn der Vergleichsoperator überladen ist und dadurch eine höhere Komplexität hat). Dieser Vergleich wird in der for-Schleife jeweils N-mal durchgeführt (&amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt;), so dass man nach der Verschachtelungsregel eine gesamte Komplexität von &amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt; erhält.&lt;br /&gt;
&lt;br /&gt;
Der Name ''lineare'' Suche rührt von diesem linearen Anwachsen der Komplexität mit der Arraygröße her.&lt;br /&gt;
&lt;br /&gt;
==Binäre Suche==&lt;br /&gt;
&lt;br /&gt;
Wie wir weiter unten zeigen werden, gestattet es diese Suchmethode, die Gesamtdauer der Suche in großen Datensätzen beträchtlich zu verringern. Die Methode beruht auf dem [http://de.wikipedia.org/wiki/Divide_and_Conquer Divide and Conquer-Prinzip], wobei die Suche in jedem Schritt rekursiv auf eine Hälfte des Datensatzes eingeschränkt wird. Weitere Details zur Methode sind [http://de.wikipedia.org/wiki/Bin%C3%A4re_Suche hier] zu finden. &lt;br /&gt;
&lt;br /&gt;
Die Methode ist nur dann anwendbar beziehungsweise effektiv, wenn folgendes gilt:&lt;br /&gt;
&lt;br /&gt;
# Auf der Eigenschaft der Daten, die zur Suche verwendet wird, ist eine Ordnung im Sinne von &amp;lt; oder &amp;gt; definiert.&lt;br /&gt;
# Wir wollen uns auf Datensätze beschränken, die schon fertig aufgebaut sind, in die also keine neuen Elemente mehr eingefügt werden, wenn man mit dem Suchen beginnt. Ist dies nicht der Fall, so ist unter Umständen die Implementierung über einen [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum Binärbaum] (siehe auch weiter unten) geschickter. &lt;br /&gt;
&lt;br /&gt;
Der folgende Algorithmus zeigt eine beispielhafte Implementierung der Methode (wir setzen wieder &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt;):&lt;br /&gt;
&lt;br /&gt;
 a = [...,...]     # array&lt;br /&gt;
 a.sort()   # sortiere über Ordnung des Schlüssels&lt;br /&gt;
 foundIndex = binSearch(a, key, 0, len(a))  # (Array, Schlüssel, von wo bis wo suchen im Array)&lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq&amp;lt;/math&amp;gt;  foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def binSearch(a, key, start, end):  # start ist 1. Index, end ist letzter Index + 1&lt;br /&gt;
    size = end - start   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if size &amp;lt;= 0:   # Bereich leer?  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return -1   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    center = (start + end)/2   # Integer Division, Ergebnis wird abgerundet, wichtig für ganzzahlige Indizes &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if a[center] == key:  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return center  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    elif a[center] &amp;lt; key:  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return binSearch(a, key, center + 1, end)  # Rekursion&lt;br /&gt;
    else:&lt;br /&gt;
        return binSearch(a, key, start + 1, center)  # Rekursion&lt;br /&gt;
&lt;br /&gt;
Zur Berechnung der Komplexität dieses Algorithmus vernachlässigen wir zunächst den Aufwand, den die Sortierung weiter oben verursacht. Dieser Schritt mag oder mag nicht zulässig sein.&lt;br /&gt;
&lt;br /&gt;
Nach der Sequenzregel haben auch alle &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;.  Es bleibt die Komplexität der Rekursion zu berechnen. Die gesamte Komplexität des Algorithmus (jetzt als Funktion f bezeichnet) setzt sich zusammen aus den oben erwähnten &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen sowie der Rekursion und ist &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f(N) = \mathcal{O}(1) + f(N/2) = \mathcal{O}(1) + \mathcal{O}(1) + f(N/4) = ... = \underbrace{\mathcal{O}(1) + ... + \mathcal{O}(1) + \underbrace{f(0)}_{\mathcal{O}(1)\, \rightarrow \,\mathrm{size-Abfrage}}}_{n+1 \,\mathrm{Terme}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Falls jetzt gilt &amp;lt;math&amp;gt; N = 2^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \rightarrow f(N) = \mathcal{O}(1) \cdot \mathcal{O}(n+1) = \mathcal{O}(n) = \mathcal{O}(\lg N) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Für große Datenmengen ist die ''binäre Suche'' also weit effizienter als die ''lineare Suche''. Verdoppelt sich beispielsweise die zu durchsuchende Datenmenge, so verdoppelt sich der Aufwand für die ''sequentielle Suche'' - bei der ''binären Suche'' hingegen benötigt man lediglich eine zusätzliche Vergleichsoperation. &lt;br /&gt;
&lt;br /&gt;
Für kleine Daten (&amp;lt;math&amp;gt; N = 4,\, 5 &amp;lt;/math&amp;gt;) ist die ''sequentielle Suche'' jedoch schneller als die ''binäre Suche'', da hier die rekursiven Funktionsaufrufe teurer als das Mehr an Vergleichen sind. Ein anderer ungünstiger Fall ist gegeben, wenn nur sehr wenige Suchanfragen erfolgen (weniger als &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; viele). Dann wird der Aufwand durch das Sortieren des Arrays dominiert, ist also &amp;lt;math&amp;gt;\mathcal{O}(N \lg N) &amp;lt;/math&amp;gt;. Auch dann ist sequentielle Suche vorzuziehen.&lt;br /&gt;
&lt;br /&gt;
Eine relativ einfache Möglichkeit, die ''binäre Suche'' zu verbessern, ist die sogenannte ''Interpolationssuche''. Hierbei wird die neue Position für die Suche, also die Mitte des Arrays, durch eine Schätzung ersetzt, die angibt, wo sich der Schlüssel innerhalb des Arrays befinden könnte. Bei der Suche in einem Telefonbuch nach dem Namen Zebra würde man ja auch nicht in der Mitte anfangen. Näheres hierzu im Buch von ''Sedgewick''.&lt;br /&gt;
&lt;br /&gt;
Um sich den Algorithmus der ''binären Suche'' klar zu machen, ist es instruktiv, sich die folgende Tabelle genauer anzusehen, die die sukzessive Belegung der Variablen bei verschiedenen Anfragen beschreibt. Die Testfälle wurden nach dem Prinzip des ''domain partitioning'' gewählt. Das zugehörige Array hat die Einträge&lt;br /&gt;
&lt;br /&gt;
 a = [2, 3, 4, 5, 6]&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align:center&amp;quot; border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;5&amp;quot; cellspacing=&amp;quot;0&amp;quot; &lt;br /&gt;
! gesuchter key   !!  start      !! end  !! size !! center !! return &amp;lt;br/&amp;gt; (-1 oder index)  !! Kommentare  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 4     ||0            || 5    ||  5   || 2   ||  2         || gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 2     || 0           || 5    ||  5   || 2   ||            || linker Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||           &lt;br /&gt;
|-&lt;br /&gt;
|       ||  0          || 1    ||  1   || 0   ||  0         || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 1     ||0            || 5    ||  5   || 2   ||            || links außerhalb&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 1    ||  1   || 0   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 0    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 6     ||0            || 5    ||  5   || 2   ||            || rechter Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    || 2    || 4   || 4          || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 5     ||0            || 5    ||  5   || 2   ||            || typischer Fall&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||3            || 5    ||  2   || 4   ||            ||  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       || 3           || 4    ||  1   || 3   || 3          || gefunden&lt;br /&gt;
|- &lt;br /&gt;
| 7     ||0            || 5    ||  5   || 2   ||            || rechts außerhalb        &lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    ||  2   || 4   ||            ||&lt;br /&gt;
|-&lt;br /&gt;
|       ||5            || 5    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Suche in einem Binärbaum ==&lt;br /&gt;
&lt;br /&gt;
Eine kurze Einführung in Binärbäume findet man [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum hier].&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum.png|text-top|300x300px|Zur Illustration von Bäumen]]&lt;br /&gt;
&lt;br /&gt;
Bäume sind zweidimensional verkettete Strukturen. Sie gehören zu den fundamentalen Datenstrukturen in der Informatik. Da man in Bäumen nicht nur Daten speichern kann, sondern auch relevante Beziehungen der Daten untereinander, festgelegt über eine Ordnung auf der vergleichenden Dateneigenschaft (''Schlüssel''), eignen sich Bäume also insbesondere, um gesuchte Daten schnell wieder auffinden zu können.&lt;br /&gt;
&lt;br /&gt;
Ein ''Binärbaum'' wie oben skizziert besteht aus einer Menge von ''Knoten'', die untereinander durch ''Kanten'' verbunden sind. Jeder Knoten hat einen linken und einen rechten Unterbaum, der auch leer sein kann (in Python ließe sich dies mit ''None'' implementieren). Führt eine Kante von Knoten A zu Knoten B, so heißt A Vater von B und B Kind von A. Es gibt genau einen Knoten ohne Vater, den man ''Wurzel'' nennt. Knoten ohne Kinder heißen ''Blätter''.&lt;br /&gt;
&lt;br /&gt;
Ein ''Suchbaum'' hat zusätzlich die Eigenschaft, dass die Schlüssel jedes Knotens sortiert sind. Alle Schlüssel im linken Unterbaum sind kleiner, alle Schlüssel im rechten Unterbaum sind größer als ihr Vater. Wir wollen hierbei annehmen, dass jeder Schlüssel pro Datensatz nur einmal vorkommt, da sich sonst die &amp;gt;- oder &amp;lt;-Relation nicht mehr strikt erfüllen ließe.&lt;br /&gt;
&lt;br /&gt;
Um in einem Baum suchen zu können, wollen wir von zwei Annahmen ausgehen:&lt;br /&gt;
# Einfügen und Suchen im Baum wechseln sich ab. (Wenn das Suchen erst beginnt, nachdem alle Einfügungen erfolgt sind, wäre ein dynamisches Array mit binärer Suche wie oben wesentlich einfacher.)&lt;br /&gt;
# Der Schlüssel, der die Anordnung bestimmt, kennt eine [http://de.wikipedia.org/wiki/Ordnungsrelation Ordnung] (&amp;lt;-Relation oder &amp;gt;-Relation).&lt;br /&gt;
&lt;br /&gt;
Der folgende Python-Code zeigt beispielhaft, wie man in einem Suchbaum suchen könnte. Der Konstruktor für einen Knoten des Suchbaums ließe sich zum Beispiel so implementieren:&lt;br /&gt;
 &lt;br /&gt;
 class Node:&lt;br /&gt;
     def __init__(self, key):&lt;br /&gt;
         self.key = key&lt;br /&gt;
         self.left = self.right = None&lt;br /&gt;
 &lt;br /&gt;
 root = ...    # Wurzel des Suchbaums&lt;br /&gt;
 nodeFound = treeSearch(root, key)   # None, falls nichts gefunden&lt;br /&gt;
 &lt;br /&gt;
 def treeSearch(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return None&lt;br /&gt;
     elif node.key == key:&lt;br /&gt;
         return node&lt;br /&gt;
     elif key &amp;lt; node.key:&lt;br /&gt;
         return treeSearch(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         return treeSearch(node.right, key)&lt;br /&gt;
&lt;br /&gt;
Daraus resultiert der folgende Suchalgorithmus:&lt;br /&gt;
&lt;br /&gt;
 def treeSort(node,array):     # dynamisches Array als 2. Argument&lt;br /&gt;
     if node is None:     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
         return None:&lt;br /&gt;
     treeSort(node.left, array)     # rekursiv&lt;br /&gt;
     array.append(node.key)     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
     treeSort(node.right, array)     # rekursiv&lt;br /&gt;
&lt;br /&gt;
Komplexität:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 f(N)=\mathcal{O}(1)+f(N_\mathrm{left})+f(N_\mathrm{right})=\mathcal{O}(1)+\mathcal{O}(1)+f(N_\mathrm{leftleft})+f(N_\mathrm{leftright})+\mathcal{O}(1)+f(N_\mathrm{rightleft})&lt;br /&gt;
 +f(N_\mathrm{leftright})=N\ast\mathcal{O}(1)=\mathcal{O}(N)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sortier-Pseudocode:&lt;br /&gt;
&lt;br /&gt;
 Sortieren:&lt;br /&gt;
     (Array) a     # unsortiert&lt;br /&gt;
     (tree) t     # zunächst leer&lt;br /&gt;
 (dynamisches Array) r     # später sortiert&lt;br /&gt;
 for e in a:&lt;br /&gt;
     t = treeInsert(t, e)&lt;br /&gt;
 treeSort(t, r)&lt;br /&gt;
&lt;br /&gt;
== Insert ==&lt;br /&gt;
&lt;br /&gt;
Was passiert wenn der key (Schlüssel) schon vorhanden ist?&lt;br /&gt;
* error (exception)&lt;br /&gt;
* nichts einfügen&lt;br /&gt;
* nichts einfügen aber einen boolean return zurückgeben (einfügen=true, false)&lt;br /&gt;
* nochmals eingefügt (z.B. in der Node Klasse)&lt;br /&gt;
&lt;br /&gt;
Wobei die ersten 3 Punkte zur Mengensemantik gehören und der letzte eine Multimenge ist.&lt;br /&gt;
&lt;br /&gt;
Algorithmus im zweiten Fall (nichts einfügen):&lt;br /&gt;
&lt;br /&gt;
 def treeInsert(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return Node(key)     # Alternative Schreibweise: node = Node(key)&lt;br /&gt;
     if node.key == key:      # und dann:&lt;br /&gt;
         return node          # pass&lt;br /&gt;
     elif key &amp;lt; node.key:     # links im Baum&lt;br /&gt;
         node.left = treeInsert(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         node.right = treeInsert(node.right, key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Remove ==&lt;br /&gt;
Fälle:&lt;br /&gt;
# key (bzw. Knoten der key enthält) ist ein Blatt =&amp;gt; einfach löschen&lt;br /&gt;
# node hat &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; linken Unterbaum oder &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; rechten Unterbaum =&amp;gt; durch Unterbaum ersetzen&lt;br /&gt;
# node hat beide Unterbäume:&lt;br /&gt;
#* Suche Vorgänger: max k &amp;lt; key (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; keys); Vorgänger ist immer ein Fall 1 oder Fall 2&lt;br /&gt;
#*:=&amp;gt; ersetze node durch Vorgänger und entferne Vorgänger&lt;br /&gt;
&lt;br /&gt;
 def treePredecessor(node):     # wird nur bei Fall 3 aufgerufen&lt;br /&gt;
     node = node.left&lt;br /&gt;
     while node.right is not None:&lt;br /&gt;
         node = node.right&lt;br /&gt;
     return node&lt;br /&gt;
 &lt;br /&gt;
 def treeRemove(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return node&lt;br /&gt;
     if key &amp;lt; node.key:&lt;br /&gt;
         node.left = treeRemove(node.left, key)&lt;br /&gt;
     elif key &amp;gt; node.key:&lt;br /&gt;
         node.right = treeRemove(node.right, key)&lt;br /&gt;
     else:&lt;br /&gt;
         if node.left is None and node.right is None:     # Fall 1&lt;br /&gt;
             node = None&lt;br /&gt;
         elif node.left is None:     # Fall 2&lt;br /&gt;
             node = node.right       # +&lt;br /&gt;
         elif node.right is None:    # Fall 2&lt;br /&gt;
             node = node.left&lt;br /&gt;
         else:&lt;br /&gt;
             pred = treePredecessor(node)&lt;br /&gt;
             node.key = pred.key&lt;br /&gt;
             node.left = treeRemove(node.left, pred.key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Komplexitätsanalyse ==&lt;br /&gt;
&lt;br /&gt;
* Pfad (Zwischen node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;): &lt;br /&gt;
**Folge von Knoten (node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt;,...,node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt;), sodass:&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;ki&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;ki+1&amp;lt;/sub&amp;gt; haben eine gemeinsame Kante.&lt;br /&gt;
[[Image:Baum_Pfad.png]]&lt;br /&gt;
(Ein Baum ist ein Graph, indem es zwischen beliebigen Knoten stets genau einen Pfad gibt.)&lt;br /&gt;
&lt;br /&gt;
* Länge eines Pfades: Anzahl der Kanten = Anzahl der Knoten - 1&lt;br /&gt;
* Tiefe eines Knotens: Pfadlänge vom Knoten zur Wurzel des Baumes.&lt;br /&gt;
* Tiefe des Baumes: maximale Tiefe eines Knotens&lt;br /&gt;
* Balance eines Baumes: maximale Tiefe(k) - minimale Tiefe(k) (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; Blätter)&lt;br /&gt;
&lt;br /&gt;
== Ungünstigster Fall von treeSearch ==&lt;br /&gt;
&lt;br /&gt;
Komplexität von treeSearch = Länge des Pfades zum Knoten wo &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; gefunden wird, oder erkannt wird, dass &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; nicht im Baum ist.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; wird nicht gefunden, aber für diese Entscheidung muss der Längste Pfad vollständig durchlaufen werden.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(T)&amp;lt;/math&amp;gt; wo T = Tiefe des Baumes&lt;br /&gt;
*: [1,2,3,4,5]:&amp;lt;br /&amp;gt; [[Image:Balance.png]]&lt;br /&gt;
*: Tiefe T = 4, Balance = 4&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; wo N = Anzahl der Elemente.&lt;br /&gt;
&lt;br /&gt;
== Aufgabe ==&lt;br /&gt;
&lt;br /&gt;
Minimiere Balance (erzeuge balancierten Baum):&lt;br /&gt;
# Einfügen in geschickter Reihenfolge (siehe Übungsaufgabe)&lt;br /&gt;
# Selbstbalancierter Baum:&lt;br /&gt;
#* Überprüfen der Balance nach jedem Einfügen&lt;br /&gt;
#* Umstrukturieren des Baumes, falls Balance &amp;gt; 1 (Suchbaum-Bedingung muss erhalten bleiben)&lt;br /&gt;
#* AVL-Bäume (älteste Variante)&lt;br /&gt;
#* Rot-Schwarz-Bäume (verbreiteste Variante)&lt;br /&gt;
#* Treaps (flexibelste Variante, siehe Übung)&lt;br /&gt;
#* Splay trees&lt;br /&gt;
#* Andersson Trees (einfachste Variante)&lt;br /&gt;
(#* Skip Lists (schnellste Variante, aber kein Binärbaum))&lt;br /&gt;
&lt;br /&gt;
== Umstrukturieren, so dass Suchbaumbedingung erhalten bleibt: ==&lt;br /&gt;
&lt;br /&gt;
Rotation: elementare Umstrukturierungen&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum_Rotation.png]]&lt;br /&gt;
&lt;br /&gt;
 def rotateRight(node):&lt;br /&gt;
     newRoot = node.left&lt;br /&gt;
     node.left = newRoot.right&lt;br /&gt;
     newRoot.right = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
 def rotateLeft(node):&lt;br /&gt;
     newRoot = node.right&lt;br /&gt;
     node.right = newRoot.left&lt;br /&gt;
     newRoot.left = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
== Balance eines Suchbaumes ==&lt;br /&gt;
&lt;br /&gt;
===Balance eines Baumes zu  definieren:===&lt;br /&gt;
[[Image:Abbildung1.jpg|250px|right]]&lt;br /&gt;
*betrachte &amp;lt;tt&amp;gt;None&amp;lt;/tt&amp;gt; als zusätzlichen Knoten : '''sentinel'''   ([[englische Sprache|engl.]] für ''Wächter'')&lt;br /&gt;
*oder defeniere speziellen sentinel-Knoten&lt;br /&gt;
*RS - Pfades : von &amp;lt;tt&amp;gt;root&amp;lt;/tt&amp;gt; &amp;amp;rarr; ''sentinel''&lt;br /&gt;
*Länge eines Pfades ''P''      '''''|P|'''''&lt;br /&gt;
* &amp;lt;u&amp;gt;die Balance eines Baumes zu defenieren:&amp;lt;/u&amp;gt;&lt;br /&gt;
: '''''B = max P '''''&amp;lt;math&amp;gt; \in &amp;lt;/math&amp;gt; '''''{RS}    |P| - min |P|'''''&lt;br /&gt;
:'''''{RS}'''''  Menge aller RS-Pfade&lt;br /&gt;
;vollständiger Baum:  Balance &amp;lt;math&amp;gt;~=0&amp;lt;/math&amp;gt;&lt;br /&gt;
:Alle Knoten(außer Blättern) haben 2 Kinder.&lt;br /&gt;
;perfect balancierter Baum:  Balance  &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 1&lt;br /&gt;
'''alternative Defenition für perfect balancierte Bäume'''&lt;br /&gt;
:Für jeden Teilbaum gilt es: rechtes und linkes Kind (für jeden Knoten) sind auch wieder perfect balancierte Bäume und ihre Höhe unterscheidet sich höchstens um '''1'''.&lt;br /&gt;
&lt;br /&gt;
===Größe===&lt;br /&gt;
[[Image:baum-v.png|700px|right]]&lt;br /&gt;
====Größe des vollständigen Baumes====&lt;br /&gt;
Ebene K hat 2 &amp;lt;sup&amp;gt;k&amp;lt;/sup&amp;gt; Knoten.&lt;br /&gt;
Falls Tiefe = d , dann N = 2&amp;lt;sup&amp;gt;0&amp;lt;/sup&amp;gt; + 2&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;.....+ 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; = 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1&lt;br /&gt;
&lt;br /&gt;
====Größe des perfect balancierten Baumes====&lt;br /&gt;
Die Tiefe d, kann nicht besser sein als vollständiger Baum&lt;br /&gt;
:'''=&amp;gt;'''N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2 &amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
;schlechteste perfect balancierter Baum : ist ein vollständiger der Tiefe  d - 1 + 1, N &amp;lt;math&amp;gt; \ge&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:'''=&amp;gt;'''  2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1)   &amp;lt;math&amp;gt;~&amp;lt;&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt;)&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
d &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt;  log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; (d + 1)&lt;br /&gt;
&lt;br /&gt;
*Ergibt die Komplexität der Suche im schlechtesten Fall: Anzahl der Vergleiche pro Knoten( = 2 bzw. = 3)&amp;lt;math&amp;gt;\ast&amp;lt;/math&amp;gt;Anzahl der Knoten&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow ~f(N)\le 2d \le 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
*'''perfekt balancierter Baum '''=&amp;gt; AVL-Baum &lt;br /&gt;
*'''balancierter Baum''':&lt;br /&gt;
::&amp;lt;math&amp;gt;\forall  N:d(N)\le c \ast d (N)&amp;lt;/math&amp;gt; und  &amp;lt;math&amp;gt;1 \le c &amp;lt; \mathcal {1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:: d ist die Tiefe von perfekt balancierten Baum&lt;br /&gt;
&lt;br /&gt;
:''Komplexität der Suche'':&amp;lt;math&amp;gt; ~f(N)\le  c\ast 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
:algorithmisch einfacher als perfekt balancierter Baum, aber fast genauso schnell&lt;br /&gt;
&lt;br /&gt;
====Selbst-balancierter Baum====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  def insertTree(node,key):&lt;br /&gt;
    if node is sentinel:       #(None = sentinel)&lt;br /&gt;
        return Node(key)       &lt;br /&gt;
    if node.key == key:&lt;br /&gt;
        return Node&lt;br /&gt;
    if key &amp;lt; node.key:&lt;br /&gt;
        Node = insertTree(node.left, key)&lt;br /&gt;
    else:&lt;br /&gt;
        node.right = insertTree(node.right, key)&lt;br /&gt;
   #optimiere Balance:&lt;br /&gt;
        return node&lt;br /&gt;
&lt;br /&gt;
===Anderson-Bäume===&lt;br /&gt;
&lt;br /&gt;
  class Node:&lt;br /&gt;
    def__init__(self, key):&lt;br /&gt;
        self.key = key&lt;br /&gt;
        self.left = selft.right = None&lt;br /&gt;
        #einfügen:&lt;br /&gt;
        self.level = 1&lt;br /&gt;
&lt;br /&gt;
:''level'' : kodiert Abstand von sentinel&lt;br /&gt;
====Regeln====&lt;br /&gt;
Es gibt vertikale Kanten(parent.level == child.level + 1 ) und horizontale Kanten(parent.level == child.level)&lt;br /&gt;
:*verfeinerter Regel :  ''level kodiert reduzierten Abstand von Setinel''  (d.b. horizontale Kanten werden nicht gezählt)&lt;br /&gt;
::(die nächste zwei Regeln sichern die Balance):&lt;br /&gt;
::*alle RS-Pfade haben die gleiche reduzierte Länge oder ''root'' hat bei allen Pfaden das gleiche Level&lt;br /&gt;
::*kein Pfad hat 2 aufeinanderer folgende horizontale Kanten&lt;br /&gt;
:*nur Kanten zum rechten Kind dürfen horizontal sein&lt;br /&gt;
:*die reduzierte Höhe jedes Blatts ist '''hr=1'''&lt;br /&gt;
&lt;br /&gt;
====Beweis====&lt;br /&gt;
Vereinfachung des Algorithmus&lt;br /&gt;
:'''Satz''':ein Anderson-Baum ist balanciert.&lt;br /&gt;
*1. Sei '''hr'''- die reduzierte Höhe&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; jeder Teilbaum enthält mindestens &amp;lt;math&amp;gt;N\ge 2^{hr} -1 &amp;lt;/math&amp;gt;  Knoten&lt;br /&gt;
&lt;br /&gt;
::a). Blätter : reduzierte Höhe 1 =&amp;gt; &amp;lt;math&amp;gt; N\ge 2^{1} - 1 = 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
::b). inneren Knoten: jeder Unterbaum hat mindestens reduzierte Höhe &amp;lt;math&amp;gt; ~hr - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt;  jeder Unterbaum hat mindestens &amp;lt;math&amp;gt; ~2^{hr} -1 &amp;lt;/math&amp;gt; Knoten&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt;N \ge 2 (2^{hr-1} -1)+1 = 2^w - 2 + 1 = 2^{hr} -1&amp;lt;/math&amp;gt;&lt;br /&gt;
::::alle RS-Pfade heben gleiche Länge&lt;br /&gt;
[[Image:Anderson.png|700px|right]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
*2. Kein Pfad hat 2 aufeinanderfolgende horizontal Kanten&lt;br /&gt;
:::&amp;lt;math&amp;gt;\Rightarrow  d \le 2 hr + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
*3. zusammen:&lt;br /&gt;
:&amp;lt;math&amp;gt;N \ge 2^{\frac {d}{2}} - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;log_{2}{ N}+1\ge log 2^{\frac {d}{2}} - 1 \ge \frac {d}{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; ~d &amp;lt; 2 log_{2}{(N + 1)}&amp;lt;/math&amp;gt;     - balancierter Baum&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
:'''Suchzeit''' : &amp;lt;math&amp;gt; ~ f(N) = \mathcal{O}(log{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Wie erreicht man die Balance===&lt;br /&gt;
====Rotation====&lt;br /&gt;
[[Image:Abbildung4.jpg|text-top|350px|Illustration von Rotation]]&amp;lt;br clear=&amp;quot;both&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
   def rotateRight (node):&lt;br /&gt;
      root = node.left&lt;br /&gt;
      node.left = node.right&lt;br /&gt;
      root.right = node&lt;br /&gt;
      return root&lt;br /&gt;
   def rotateLeft(node):&lt;br /&gt;
      root = node.right&lt;br /&gt;
      node.right = node.left&lt;br /&gt;
      root.left = node&lt;br /&gt;
      return root&lt;br /&gt;
&lt;br /&gt;
====Optimierung der Balance====&lt;br /&gt;
   &lt;br /&gt;
&lt;br /&gt;
       if node.left is not sentinel and node.level==node.left.level:&lt;br /&gt;
            node &amp;gt; rotateRight(node)&lt;br /&gt;
       if node.right is not sentinel and node.right.right is  not sentinel and node.level==rotate.right.right.level:&lt;br /&gt;
            node = rotateLeft(node)&lt;br /&gt;
            node.level += 1&lt;/div&gt;</summary>
		<author><name>Nata</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1496</id>
		<title>Suchen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1496"/>
				<updated>2008-05-29T00:19:57Z</updated>
		
		<summary type="html">&lt;p&gt;Nata: /* Rotation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Es wäre super wenn jemand ganz kurz schreiben könnte, was am Donnerstag zu den Funktionen treeInsert/Remove/HasKey gemacht wurde, was man ja für den aktuellen Übungszettel braucht. Danke :-)&amp;lt;br /&amp;gt;&lt;br /&gt;
/edit: an eine Funktion &amp;quot;HasKey&amp;quot; kann ich mich aus der Vorlesung nicht erinnern. Wenn die jemand hat, bitte eintragen. - Protokollant&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--Hallo, ich kenne leider deinen Namen nicht, ich habe den Wiki-Eintrag von letztem Donnerstag gemacht.Da das Thema 'Dynamisches Array' noch Thema von letzter Woche war, hab ich die Wiederholung der amortisierten Kosten von diesem Mittwoch noch bei Effizienz eingetragen, du musst es also nicht noch mal in deinem Wiki eintragen. Du kannst dir aber gerne mal anschauen was ich geschrieben habe, vielleicht fallen dir noch ein paar Sachen ein, die man hätte besser machen können. lg, Franziska.--&amp;gt;&lt;br /&gt;
Das Suchen ist eine grundlegende Operation in der Informatik. Viele Probleme in der Informatik können auf Suchaufgaben zurückgeführt werden.&lt;br /&gt;
&lt;br /&gt;
Gemeint ist mit Suchen das Wiederauffinden einer oder mehrerer Datensätze aus einer Menge von früher gespeicherten Datensätzen. Ein paar einleitende Worte zum Suchproblem findet man [http://de.wikipedia.org/wiki/Suche hier].&lt;br /&gt;
&lt;br /&gt;
== Überblick verschiedener Suchmethoden ==&lt;br /&gt;
&lt;br /&gt;
Um sich der Vielseitigkeit von Suchproblemen bewusst zu werden, ist es sinnvoll, sich einen Überblick über verschiedene Suchmethoden zu verschaffen. &lt;br /&gt;
&lt;br /&gt;
Hier sei auch auf einen bereits existierenden Wikipedia-Artikel zu [http://de.wikipedia.org/wiki/Suchverfahren Suchverfahren] verwiesen.&lt;br /&gt;
&lt;br /&gt;
Allen gemeinsam ist die grundlegende Aufgabe, ein Datenelement mit bestimmten Eigenschaften aus einer großen Menge von Datenelementen zu selektieren.&lt;br /&gt;
Dies kann, natürlich ohne jeden Anspruch auf Vollständigkeit, nach einer der jetzt diskutierten Methoden geschehen:&lt;br /&gt;
&lt;br /&gt;
* '''Schlüsselsuche''': meint das Suchen von Elementen mit bestimmtem Schlüssel; ein klassisches Beispiel wäre das Suchen in einem Wörterbuch,  die Schlüssel entsprechen hier den Wörtern, die Datensätze wären die zu den Wörtern gehörigen Eintragungen.&lt;br /&gt;
&lt;br /&gt;
* '''Bereichssuche''': Im Allgemeinen meint die Bereichssuche in n-Dimensionen die Selektion von Elementen mit Eigenschaften aus einem bestimmten n-dimensionalen Volumen. Im eindimensionalen Fall will man alle Elemente finden, deren Eigenschaft(en) in einem bestimmten Intervall liegen. Die Verallgemeinerung auf n-Dimensionen ist offensichtlich. Ein Beispiel für die Bereichssuche in einer 3D-Kugel wäre ein Handy mit Geolokalisierung, welches alle Restaurants in einem Umkreis von 500m findet. Lineare Ungleichungen werden graphisch durch [http://de.wikipedia.org/wiki/Hyperebene Hyperebenen] repräsentiert. In 2D sind diese Hyperebenen Geraden. Die Ungleichungen können dann den Lösungsraum in irgendeiner Form begrenzen.&lt;br /&gt;
&lt;br /&gt;
* '''Ähnlichkeitssuche''': Finde Elemente, die gegebenen Eigenschaften möglichst ähnlich sind. Ein prominentes Beispiel ist Google (=Ähnlichkeit zwischen Suchbegriffen und Dokumenten) oder das Suchen des nächstengelegenen Restaurants (Ähnlichkeit zwischen eigener Position und Position des Restaurants). Ein wichtigster Spezialfall ist die ''nächste-nachbar Suche''.&lt;br /&gt;
&lt;br /&gt;
* '''Graphensuche''': Hier wäre beispielsweise das Problem optimaler Wege zu nennen (Navigationssuche). Dieser Punkt wird später im Verlauf der Vorlesung noch einmal aufgegriffen werden.&lt;br /&gt;
&lt;br /&gt;
Im jetzt Folgenden wird nur noch die ''Schlüsselsuche'' betrachtet werden.&lt;br /&gt;
&lt;br /&gt;
==Sequentielle Suche==&lt;br /&gt;
&lt;br /&gt;
Die ''sequentielle'' oder ''lineare'' Suche ist die 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 = sequentialSearch(a, key) &lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq &amp;lt;/math&amp;gt; foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def sequentialSearch(a, key):&lt;br /&gt;
    for i in range(len(a)):&lt;br /&gt;
        if a[i] == key:  # bzw. allgemeiner a[i].key == key &lt;br /&gt;
            return i&lt;br /&gt;
    return -1&lt;br /&gt;
&lt;br /&gt;
Wir wollen jetzt die Komplexität dieses Algorithmus bestimmen, wobei die Problemgröße durch &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt; gegeben ist. &lt;br /&gt;
&lt;br /&gt;
Dabei nimmt man an, dass der innerste Vergleich (a[i] == key) jeweils &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt; ist (diese Annahme könnte verletzt sein, wenn der Vergleichsoperator überladen ist und dadurch eine höhere Komplexität hat). Dieser Vergleich wird in der for-Schleife jeweils N-mal durchgeführt (&amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt;), so dass man nach der Verschachtelungsregel eine gesamte Komplexität von &amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt; erhält.&lt;br /&gt;
&lt;br /&gt;
Der Name ''lineare'' Suche rührt von diesem linearen Anwachsen der Komplexität mit der Arraygröße her.&lt;br /&gt;
&lt;br /&gt;
==Binäre Suche==&lt;br /&gt;
&lt;br /&gt;
Wie wir weiter unten zeigen werden, gestattet es diese Suchmethode, die Gesamtdauer der Suche in großen Datensätzen beträchtlich zu verringern. Die Methode beruht auf dem [http://de.wikipedia.org/wiki/Divide_and_Conquer Divide and Conquer-Prinzip], wobei die Suche in jedem Schritt rekursiv auf eine Hälfte des Datensatzes eingeschränkt wird. Weitere Details zur Methode sind [http://de.wikipedia.org/wiki/Bin%C3%A4re_Suche hier] zu finden. &lt;br /&gt;
&lt;br /&gt;
Die Methode ist nur dann anwendbar beziehungsweise effektiv, wenn folgendes gilt:&lt;br /&gt;
&lt;br /&gt;
# Auf der Eigenschaft der Daten, die zur Suche verwendet wird, ist eine Ordnung im Sinne von &amp;lt; oder &amp;gt; definiert.&lt;br /&gt;
# Wir wollen uns auf Datensätze beschränken, die schon fertig aufgebaut sind, in die also keine neuen Elemente mehr eingefügt werden, wenn man mit dem Suchen beginnt. Ist dies nicht der Fall, so ist unter Umständen die Implementierung über einen [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum Binärbaum] (siehe auch weiter unten) geschickter. &lt;br /&gt;
&lt;br /&gt;
Der folgende Algorithmus zeigt eine beispielhafte Implementierung der Methode (wir setzen wieder &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt;):&lt;br /&gt;
&lt;br /&gt;
 a = [...,...]     # array&lt;br /&gt;
 a.sort()   # sortiere über Ordnung des Schlüssels&lt;br /&gt;
 foundIndex = binSearch(a, key, 0, len(a))  # (Array, Schlüssel, von wo bis wo suchen im Array)&lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq&amp;lt;/math&amp;gt;  foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def binSearch(a, key, start, end):  # start ist 1. Index, end ist letzter Index + 1&lt;br /&gt;
    size = end - start   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if size &amp;lt;= 0:   # Bereich leer?  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return -1   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    center = (start + end)/2   # Integer Division, Ergebnis wird abgerundet, wichtig für ganzzahlige Indizes &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if a[center] == key:  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return center  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    elif a[center] &amp;lt; key:  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return binSearch(a, key, center + 1, end)  # Rekursion&lt;br /&gt;
    else:&lt;br /&gt;
        return binSearch(a, key, start + 1, center)  # Rekursion&lt;br /&gt;
&lt;br /&gt;
Zur Berechnung der Komplexität dieses Algorithmus vernachlässigen wir zunächst den Aufwand, den die Sortierung weiter oben verursacht. Dieser Schritt mag oder mag nicht zulässig sein.&lt;br /&gt;
&lt;br /&gt;
Nach der Sequenzregel haben auch alle &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;.  Es bleibt die Komplexität der Rekursion zu berechnen. Die gesamte Komplexität des Algorithmus (jetzt als Funktion f bezeichnet) setzt sich zusammen aus den oben erwähnten &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen sowie der Rekursion und ist &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f(N) = \mathcal{O}(1) + f(N/2) = \mathcal{O}(1) + \mathcal{O}(1) + f(N/4) = ... = \underbrace{\mathcal{O}(1) + ... + \mathcal{O}(1) + \underbrace{f(0)}_{\mathcal{O}(1)\, \rightarrow \,\mathrm{size-Abfrage}}}_{n+1 \,\mathrm{Terme}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Falls jetzt gilt &amp;lt;math&amp;gt; N = 2^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \rightarrow f(N) = \mathcal{O}(1) \cdot \mathcal{O}(n+1) = \mathcal{O}(n) = \mathcal{O}(\lg N) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Für große Datenmengen ist die ''binäre Suche'' also weit effizienter als die ''lineare Suche''. Verdoppelt sich beispielsweise die zu durchsuchende Datenmenge, so verdoppelt sich der Aufwand für die ''sequentielle Suche'' - bei der ''binären Suche'' hingegen benötigt man lediglich eine zusätzliche Vergleichsoperation. &lt;br /&gt;
&lt;br /&gt;
Für kleine Daten (&amp;lt;math&amp;gt; N = 4,\, 5 &amp;lt;/math&amp;gt;) ist die ''sequentielle Suche'' jedoch schneller als die ''binäre Suche'', da hier die rekursiven Funktionsaufrufe teurer als das Mehr an Vergleichen sind. Ein anderer ungünstiger Fall ist gegeben, wenn nur sehr wenige Suchanfragen erfolgen (weniger als &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; viele). Dann wird der Aufwand durch das Sortieren des Arrays dominiert, ist also &amp;lt;math&amp;gt;\mathcal{O}(N \lg N) &amp;lt;/math&amp;gt;. Auch dann ist sequentielle Suche vorzuziehen.&lt;br /&gt;
&lt;br /&gt;
Eine relativ einfache Möglichkeit, die ''binäre Suche'' zu verbessern, ist die sogenannte ''Interpolationssuche''. Hierbei wird die neue Position für die Suche, also die Mitte des Arrays, durch eine Schätzung ersetzt, die angibt, wo sich der Schlüssel innerhalb des Arrays befinden könnte. Bei der Suche in einem Telefonbuch nach dem Namen Zebra würde man ja auch nicht in der Mitte anfangen. Näheres hierzu im Buch von ''Sedgewick''.&lt;br /&gt;
&lt;br /&gt;
Um sich den Algorithmus der ''binären Suche'' klar zu machen, ist es instruktiv, sich die folgende Tabelle genauer anzusehen, die die sukzessive Belegung der Variablen bei verschiedenen Anfragen beschreibt. Die Testfälle wurden nach dem Prinzip des ''domain partitioning'' gewählt. Das zugehörige Array hat die Einträge&lt;br /&gt;
&lt;br /&gt;
 a = [2, 3, 4, 5, 6]&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align:center&amp;quot; border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;5&amp;quot; cellspacing=&amp;quot;0&amp;quot; &lt;br /&gt;
! gesuchter key   !!  start      !! end  !! size !! center !! return &amp;lt;br/&amp;gt; (-1 oder index)  !! Kommentare  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 4     ||0            || 5    ||  5   || 2   ||  2         || gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 2     || 0           || 5    ||  5   || 2   ||            || linker Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||           &lt;br /&gt;
|-&lt;br /&gt;
|       ||  0          || 1    ||  1   || 0   ||  0         || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 1     ||0            || 5    ||  5   || 2   ||            || links außerhalb&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 1    ||  1   || 0   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 0    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 6     ||0            || 5    ||  5   || 2   ||            || rechter Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    || 2    || 4   || 4          || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 5     ||0            || 5    ||  5   || 2   ||            || typischer Fall&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||3            || 5    ||  2   || 4   ||            ||  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       || 3           || 4    ||  1   || 3   || 3          || gefunden&lt;br /&gt;
|- &lt;br /&gt;
| 7     ||0            || 5    ||  5   || 2   ||            || rechts außerhalb        &lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    ||  2   || 4   ||            ||&lt;br /&gt;
|-&lt;br /&gt;
|       ||5            || 5    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Suche in einem Binärbaum ==&lt;br /&gt;
&lt;br /&gt;
Eine kurze Einführung in Binärbäume findet man [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum hier].&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum.png|text-top|300x300px|Zur Illustration von Bäumen]]&lt;br /&gt;
&lt;br /&gt;
Bäume sind zweidimensional verkettete Strukturen. Sie gehören zu den fundamentalen Datenstrukturen in der Informatik. Da man in Bäumen nicht nur Daten speichern kann, sondern auch relevante Beziehungen der Daten untereinander, festgelegt über eine Ordnung auf der vergleichenden Dateneigenschaft (''Schlüssel''), eignen sich Bäume also insbesondere, um gesuchte Daten schnell wieder auffinden zu können.&lt;br /&gt;
&lt;br /&gt;
Ein ''Binärbaum'' wie oben skizziert besteht aus einer Menge von ''Knoten'', die untereinander durch ''Kanten'' verbunden sind. Jeder Knoten hat einen linken und einen rechten Unterbaum, der auch leer sein kann (in Python ließe sich dies mit ''None'' implementieren). Führt eine Kante von Knoten A zu Knoten B, so heißt A Vater von B und B Kind von A. Es gibt genau einen Knoten ohne Vater, den man ''Wurzel'' nennt. Knoten ohne Kinder heißen ''Blätter''.&lt;br /&gt;
&lt;br /&gt;
Ein ''Suchbaum'' hat zusätzlich die Eigenschaft, dass die Schlüssel jedes Knotens sortiert sind. Alle Schlüssel im linken Unterbaum sind kleiner, alle Schlüssel im rechten Unterbaum sind größer als ihr Vater. Wir wollen hierbei annehmen, dass jeder Schlüssel pro Datensatz nur einmal vorkommt, da sich sonst die &amp;gt;- oder &amp;lt;-Relation nicht mehr strikt erfüllen ließe.&lt;br /&gt;
&lt;br /&gt;
Um in einem Baum suchen zu können, wollen wir von zwei Annahmen ausgehen:&lt;br /&gt;
# Einfügen und Suchen im Baum wechseln sich ab. (Wenn das Suchen erst beginnt, nachdem alle Einfügungen erfolgt sind, wäre ein dynamisches Array mit binärer Suche wie oben wesentlich einfacher.)&lt;br /&gt;
# Der Schlüssel, der die Anordnung bestimmt, kennt eine [http://de.wikipedia.org/wiki/Ordnungsrelation Ordnung] (&amp;lt;-Relation oder &amp;gt;-Relation).&lt;br /&gt;
&lt;br /&gt;
Der folgende Python-Code zeigt beispielhaft, wie man in einem Suchbaum suchen könnte. Der Konstruktor für einen Knoten des Suchbaums ließe sich zum Beispiel so implementieren:&lt;br /&gt;
 &lt;br /&gt;
 class Node:&lt;br /&gt;
     def __init__(self, key):&lt;br /&gt;
         self.key = key&lt;br /&gt;
         self.left = self.right = None&lt;br /&gt;
 &lt;br /&gt;
 root = ...    # Wurzel des Suchbaums&lt;br /&gt;
 nodeFound = treeSearch(root, key)   # None, falls nichts gefunden&lt;br /&gt;
 &lt;br /&gt;
 def treeSearch(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return None&lt;br /&gt;
     elif node.key == key:&lt;br /&gt;
         return node&lt;br /&gt;
     elif key &amp;lt; node.key:&lt;br /&gt;
         return treeSearch(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         return treeSearch(node.right, key)&lt;br /&gt;
&lt;br /&gt;
Daraus resultiert der folgende Suchalgorithmus:&lt;br /&gt;
&lt;br /&gt;
 def treeSort(node,array):     # dynamisches Array als 2. Argument&lt;br /&gt;
     if node is None:     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
         return None:&lt;br /&gt;
     treeSort(node.left, array)     # rekursiv&lt;br /&gt;
     array.append(node.key)     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
     treeSort(node.right, array)     # rekursiv&lt;br /&gt;
&lt;br /&gt;
Komplexität:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 f(N)=\mathcal{O}(1)+f(N_\mathrm{left})+f(N_\mathrm{right})=\mathcal{O}(1)+\mathcal{O}(1)+f(N_\mathrm{leftleft})+f(N_\mathrm{leftright})+\mathcal{O}(1)+f(N_\mathrm{rightleft})&lt;br /&gt;
 +f(N_\mathrm{leftright})=N\ast\mathcal{O}(1)=\mathcal{O}(N)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sortier-Pseudocode:&lt;br /&gt;
&lt;br /&gt;
 Sortieren:&lt;br /&gt;
     (Array) a     # unsortiert&lt;br /&gt;
     (tree) t     # zunächst leer&lt;br /&gt;
 (dynamisches Array) r     # später sortiert&lt;br /&gt;
 for e in a:&lt;br /&gt;
     t = treeInsert(t, e)&lt;br /&gt;
 treeSort(t, r)&lt;br /&gt;
&lt;br /&gt;
== Insert ==&lt;br /&gt;
&lt;br /&gt;
Was passiert wenn der key (Schlüssel) schon vorhanden ist?&lt;br /&gt;
* error (exception)&lt;br /&gt;
* nichts einfügen&lt;br /&gt;
* nichts einfügen aber einen boolean return zurückgeben (einfügen=true, false)&lt;br /&gt;
* nochmals eingefügt (z.B. in der Node Klasse)&lt;br /&gt;
&lt;br /&gt;
Wobei die ersten 3 Punkte zur Mengensemantik gehören und der letzte eine Multimenge ist.&lt;br /&gt;
&lt;br /&gt;
Algorithmus im zweiten Fall (nichts einfügen):&lt;br /&gt;
&lt;br /&gt;
 def treeInsert(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return Node(key)     # Alternative Schreibweise: node = Node(key)&lt;br /&gt;
     if node.key == key:      # und dann:&lt;br /&gt;
         return node          # pass&lt;br /&gt;
     elif key &amp;lt; node.key:     # links im Baum&lt;br /&gt;
         node.left = treeInsert(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         node.right = treeInsert(node.right, key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Remove ==&lt;br /&gt;
Fälle:&lt;br /&gt;
# key (bzw. Knoten der key enthält) ist ein Blatt =&amp;gt; einfach löschen&lt;br /&gt;
# node hat &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; linken Unterbaum oder &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; rechten Unterbaum =&amp;gt; durch Unterbaum ersetzen&lt;br /&gt;
# node hat beide Unterbäume:&lt;br /&gt;
#* Suche Vorgänger: max k &amp;lt; key (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; keys); Vorgänger ist immer ein Fall 1 oder Fall 2&lt;br /&gt;
#*:=&amp;gt; ersetze node durch Vorgänger und entferne Vorgänger&lt;br /&gt;
&lt;br /&gt;
 def treePredecessor(node):     # wird nur bei Fall 3 aufgerufen&lt;br /&gt;
     node = node.left&lt;br /&gt;
     while node.right is not None:&lt;br /&gt;
         node = node.right&lt;br /&gt;
     return node&lt;br /&gt;
 &lt;br /&gt;
 def treeRemove(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return node&lt;br /&gt;
     if key &amp;lt; node.key:&lt;br /&gt;
         node.left = treeRemove(node.left, key)&lt;br /&gt;
     elif key &amp;gt; node.key:&lt;br /&gt;
         node.right = treeRemove(node.right, key)&lt;br /&gt;
     else:&lt;br /&gt;
         if node.left is None and node.right is None:     # Fall 1&lt;br /&gt;
             node = None&lt;br /&gt;
         elif node.left is None:     # Fall 2&lt;br /&gt;
             node = node.right       # +&lt;br /&gt;
         elif node.right is None:    # Fall 2&lt;br /&gt;
             node = node.left&lt;br /&gt;
         else:&lt;br /&gt;
             pred = treePredecessor(node)&lt;br /&gt;
             node.key = pred.key&lt;br /&gt;
             node.left = treeRemove(node.left, pred.key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Komplexitätsanalyse ==&lt;br /&gt;
&lt;br /&gt;
* Pfad (Zwischen node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;): &lt;br /&gt;
**Folge von Knoten (node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt;,...,node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt;), sodass:&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;ki&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;ki+1&amp;lt;/sub&amp;gt; haben eine gemeinsame Kante.&lt;br /&gt;
[[Image:Baum_Pfad.png]]&lt;br /&gt;
(Ein Baum ist ein Graph, indem es zwischen beliebigen Knoten stets genau einen Pfad gibt.)&lt;br /&gt;
&lt;br /&gt;
* Länge eines Pfades: Anzahl der Kanten = Anzahl der Knoten - 1&lt;br /&gt;
* Tiefe eines Knotens: Pfadlänge vom Knoten zur Wurzel des Baumes.&lt;br /&gt;
* Tiefe des Baumes: maximale Tiefe eines Knotens&lt;br /&gt;
* Balance eines Baumes: maximale Tiefe(k) - minimale Tiefe(k) (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; Blätter)&lt;br /&gt;
&lt;br /&gt;
== Ungünstigster Fall von treeSearch ==&lt;br /&gt;
&lt;br /&gt;
Komplexität von treeSearch = Länge des Pfades zum Knoten wo &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; gefunden wird, oder erkannt wird, dass &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; nicht im Baum ist.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; wird nicht gefunden, aber für diese Entscheidung muss der Längste Pfad vollständig durchlaufen werden.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(T)&amp;lt;/math&amp;gt; wo T = Tiefe des Baumes&lt;br /&gt;
*: [1,2,3,4,5]:&amp;lt;br /&amp;gt; [[Image:Balance.png]]&lt;br /&gt;
*: Tiefe T = 4, Balance = 4&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; wo N = Anzahl der Elemente.&lt;br /&gt;
&lt;br /&gt;
== Aufgabe ==&lt;br /&gt;
&lt;br /&gt;
Minimiere Balance (erzeuge balancierten Baum):&lt;br /&gt;
# Einfügen in geschickter Reihenfolge (siehe Übungsaufgabe)&lt;br /&gt;
# Selbstbalancierter Baum:&lt;br /&gt;
#* Überprüfen der Balance nach jedem Einfügen&lt;br /&gt;
#* Umstrukturieren des Baumes, falls Balance &amp;gt; 1 (Suchbaum-Bedingung muss erhalten bleiben)&lt;br /&gt;
#* AVL-Bäume (älteste Variante)&lt;br /&gt;
#* Rot-Schwarz-Bäume (verbreiteste Variante)&lt;br /&gt;
#* Treaps (flexibelste Variante, siehe Übung)&lt;br /&gt;
#* Splay trees&lt;br /&gt;
#* Andersson Trees (einfachste Variante)&lt;br /&gt;
(#* Skip Lists (schnellste Variante, aber kein Binärbaum))&lt;br /&gt;
&lt;br /&gt;
== Umstrukturieren, so dass Suchbaumbedingung erhalten bleibt: ==&lt;br /&gt;
&lt;br /&gt;
Rotation: elementare Umstrukturierungen&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum_Rotation.png]]&lt;br /&gt;
&lt;br /&gt;
 def rotateRight(node):&lt;br /&gt;
     newRoot = node.left&lt;br /&gt;
     node.left = newRoot.right&lt;br /&gt;
     newRoot.right = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
 def rotateLeft(node):&lt;br /&gt;
     newRoot = node.right&lt;br /&gt;
     node.right = newRoot.left&lt;br /&gt;
     newRoot.left = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
== Balance eines Suchbaumes ==&lt;br /&gt;
&lt;br /&gt;
===Balance eines Baumes zu  definieren:===&lt;br /&gt;
[[Image:Abbildung1.jpg|250px|right]]&lt;br /&gt;
*betrachte &amp;lt;tt&amp;gt;None&amp;lt;/tt&amp;gt; als zusätzlichen Knoten : '''sentinel'''   ([[englische Sprache|engl.]] für ''Wächter'')&lt;br /&gt;
*oder defeniere speziellen sentinel-Knoten&lt;br /&gt;
*RS - Pfades : von &amp;lt;tt&amp;gt;root&amp;lt;/tt&amp;gt; &amp;amp;rarr; ''sentinel''&lt;br /&gt;
*Länge eines Pfades ''P''      '''''|P|'''''&lt;br /&gt;
* &amp;lt;u&amp;gt;die Balance eines Baumes zu defenieren:&amp;lt;/u&amp;gt;&lt;br /&gt;
: '''''B = max P '''''&amp;lt;math&amp;gt; \in &amp;lt;/math&amp;gt; '''''{RS}    |P| - min |P|'''''&lt;br /&gt;
:'''''{RS}'''''  Menge aller RS-Pfade&lt;br /&gt;
;vollständiger Baum:  Balance &amp;lt;math&amp;gt;~=0&amp;lt;/math&amp;gt;&lt;br /&gt;
:Alle Knoten(außer Blättern) haben 2 Kinder.&lt;br /&gt;
;perfect balancierter Baum:  Balance  &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 1&lt;br /&gt;
'''alternative Defenition für perfect balancierte Bäume'''&lt;br /&gt;
:Für jeden Teilbaum gilt es: rechtes und linkes Kind (für jeden Knoten) sind auch wieder perfect balancierte Bäume und ihre Höhe unterscheidet sich höchstens um '''1'''.&lt;br /&gt;
&lt;br /&gt;
===Größe===&lt;br /&gt;
[[Image:baum-v.png|700px|right]]&lt;br /&gt;
====Größe des vollständigen Baumes====&lt;br /&gt;
Ebene K hat 2 &amp;lt;sup&amp;gt;k&amp;lt;/sup&amp;gt; Knoten.&lt;br /&gt;
Falls Tiefe = d , dann N = 2&amp;lt;sup&amp;gt;0&amp;lt;/sup&amp;gt; + 2&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;.....+ 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; = 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1&lt;br /&gt;
&lt;br /&gt;
====Größe des perfect balancierten Baumes====&lt;br /&gt;
Die Tiefe d, kann nicht besser sein als vollständiger Baum&lt;br /&gt;
:'''=&amp;gt;'''N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2 &amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
;schlechteste perfect balancierter Baum : ist ein vollständiger der Tiefe  d - 1 + 1, N &amp;lt;math&amp;gt; \ge&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:'''=&amp;gt;'''  2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1)   &amp;lt;math&amp;gt;~&amp;lt;&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt;)&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
d &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt;  log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; (d + 1)&lt;br /&gt;
&lt;br /&gt;
*Ergibt die Komplexität der Suche im schlechtesten Fall: Anzahl der Vergleiche pro Knoten( = 2 bzw. = 3)&amp;lt;math&amp;gt;\ast&amp;lt;/math&amp;gt;Anzahl der Knoten&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow ~f(N)\le 2d \le 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
*'''perfekt balancierter Baum '''=&amp;gt; AVL-Baum &lt;br /&gt;
*'''balancierter Baum''':&lt;br /&gt;
::&amp;lt;math&amp;gt;\forall  N:d(N)\le c \ast d (N)&amp;lt;/math&amp;gt; und  &amp;lt;math&amp;gt;1 \le c &amp;lt; \mathcal {1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:: d ist die Tiefe von perfekt balancierten Baum&lt;br /&gt;
&lt;br /&gt;
:''Komplexität der Suche'':&amp;lt;math&amp;gt; ~f(N)\le  c\ast 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
:algorithmisch einfacher als perfekt balancierter Baum, aber fast genauso schnell&lt;br /&gt;
&lt;br /&gt;
====Selbst-balancierter Baum====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  def insertTree(node,key):&lt;br /&gt;
    if node is sentinel:       #(None = sentinel)&lt;br /&gt;
        return Node(key)       &lt;br /&gt;
    if node.key == key:&lt;br /&gt;
        return Node&lt;br /&gt;
    if key &amp;lt; node.key:&lt;br /&gt;
        Node = insertTree(node.left, key)&lt;br /&gt;
    else:&lt;br /&gt;
        node.right = insertTree(node.right, key)&lt;br /&gt;
   #optimiere Balance:&lt;br /&gt;
        return node&lt;br /&gt;
&lt;br /&gt;
===Anderson-Bäume===&lt;br /&gt;
&lt;br /&gt;
  class Node:&lt;br /&gt;
    def__init__(self, key):&lt;br /&gt;
        self.key = key&lt;br /&gt;
        self.left = selft.right = None&lt;br /&gt;
        #einfügen:&lt;br /&gt;
        self.level = 1&lt;br /&gt;
&lt;br /&gt;
:''level'' : kodiert Abstand von sentinel&lt;br /&gt;
====Regeln====&lt;br /&gt;
Es gibt vertikale Kanten(parent.level == child.level + 1 ) und horizontale Kanten(parent.level == child.level)&lt;br /&gt;
:*verfeinerter Regel :  ''level kodiert reduzierten Abstand von Setinel''  (d.b. horizontale Kanten werden nicht gezählt)&lt;br /&gt;
::(die nächste zwei Regeln sichern die Balance):&lt;br /&gt;
::*alle RS-Pfade haben die gleiche reduzierte Länge oder ''root'' hat bei allen Pfaden das gleiche Level&lt;br /&gt;
::*kein Pfad hat 2 aufeinanderer folgende horizontale Kanten&lt;br /&gt;
:*nur Kanten zum rechten Kind dürfen horizontal sein&lt;br /&gt;
:*die reduzierte Höhe jedes Blatts ist '''hr=1'''&lt;br /&gt;
&lt;br /&gt;
====Beweis====&lt;br /&gt;
Vereinfachung des Algorithmus&lt;br /&gt;
:'''Satz''':ein Anderson-Baum ist balanciert.&lt;br /&gt;
*1. Sei '''hr'''- die reduzierte Höhe&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; jeder Teilbaum enthält mindestens &amp;lt;math&amp;gt;N\ge 2^{hr} -1 &amp;lt;/math&amp;gt;  Knoten&lt;br /&gt;
&lt;br /&gt;
::a). Blätter : reduzierte Höhe 1 =&amp;gt; &amp;lt;math&amp;gt; N\ge 2^{1} - 1 = 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
::b). inneren Knoten: jeder Unterbaum hat mindestens reduzierte Höhe &amp;lt;math&amp;gt; ~hr - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt;  jeder Unterbaum hat mindestens &amp;lt;math&amp;gt; ~2^{hr} -1 &amp;lt;/math&amp;gt; Knoten&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt;N \ge 2 (2^{hr-1} -1)+1 = 2^w - 2 + 1 = 2^{hr} -1&amp;lt;/math&amp;gt;&lt;br /&gt;
::::alle RS-Pfade heben gleiche Länge&lt;br /&gt;
[[Image:Anderson.png|700px|right]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
*2. Kein Pfad hat 2 aufeinanderfolgende horizontal Kanten&lt;br /&gt;
:::&amp;lt;math&amp;gt;\Rightarrow  d \le 2 hr + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
*3. zusammen:&lt;br /&gt;
:&amp;lt;math&amp;gt;N \ge 2^{\frac {d}{2}} - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;log_{2}{ N}+1\ge log 2^{\frac {d}{2}} - 1 \ge \frac {d}{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; ~d &amp;lt; 2 log_{2}{(N + 1)}&amp;lt;/math&amp;gt;     - balancierter Baum&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
:'''Suchzeit''' : &amp;lt;math&amp;gt; ~ f(N) = \mathcal{O}(log{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Wie erreicht man die Balance===&lt;br /&gt;
====Rotation====&lt;br /&gt;
[[Image:Abbildung4.jpg|text-top|350px|Illustration von Rotation]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
   def rotateRight (node):&lt;br /&gt;
      root = node.left&lt;br /&gt;
      node.left = node.right&lt;br /&gt;
      root.right = node&lt;br /&gt;
      return root&lt;br /&gt;
   def rotateLeft(node):&lt;br /&gt;
      root = node.right&lt;br /&gt;
      node.right = node.left&lt;br /&gt;
      root.left = node&lt;br /&gt;
      return root&lt;br /&gt;
&lt;br /&gt;
====Optimierung der Balance====&lt;br /&gt;
   &lt;br /&gt;
&lt;br /&gt;
       if node.left is not sentinel and node.level==node.left.level:&lt;br /&gt;
            node &amp;gt; rotateRight(node)&lt;br /&gt;
       if node.right is not sentinel and node.right.right is  not sentinel and node.level==rotate.right.right.level:&lt;br /&gt;
            node = rotateLeft(node)&lt;br /&gt;
            node.level += 1&lt;/div&gt;</summary>
		<author><name>Nata</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1495</id>
		<title>Suchen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1495"/>
				<updated>2008-05-29T00:17:44Z</updated>
		
		<summary type="html">&lt;p&gt;Nata: /* Rotation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Es wäre super wenn jemand ganz kurz schreiben könnte, was am Donnerstag zu den Funktionen treeInsert/Remove/HasKey gemacht wurde, was man ja für den aktuellen Übungszettel braucht. Danke :-)&amp;lt;br /&amp;gt;&lt;br /&gt;
/edit: an eine Funktion &amp;quot;HasKey&amp;quot; kann ich mich aus der Vorlesung nicht erinnern. Wenn die jemand hat, bitte eintragen. - Protokollant&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--Hallo, ich kenne leider deinen Namen nicht, ich habe den Wiki-Eintrag von letztem Donnerstag gemacht.Da das Thema 'Dynamisches Array' noch Thema von letzter Woche war, hab ich die Wiederholung der amortisierten Kosten von diesem Mittwoch noch bei Effizienz eingetragen, du musst es also nicht noch mal in deinem Wiki eintragen. Du kannst dir aber gerne mal anschauen was ich geschrieben habe, vielleicht fallen dir noch ein paar Sachen ein, die man hätte besser machen können. lg, Franziska.--&amp;gt;&lt;br /&gt;
Das Suchen ist eine grundlegende Operation in der Informatik. Viele Probleme in der Informatik können auf Suchaufgaben zurückgeführt werden.&lt;br /&gt;
&lt;br /&gt;
Gemeint ist mit Suchen das Wiederauffinden einer oder mehrerer Datensätze aus einer Menge von früher gespeicherten Datensätzen. Ein paar einleitende Worte zum Suchproblem findet man [http://de.wikipedia.org/wiki/Suche hier].&lt;br /&gt;
&lt;br /&gt;
== Überblick verschiedener Suchmethoden ==&lt;br /&gt;
&lt;br /&gt;
Um sich der Vielseitigkeit von Suchproblemen bewusst zu werden, ist es sinnvoll, sich einen Überblick über verschiedene Suchmethoden zu verschaffen. &lt;br /&gt;
&lt;br /&gt;
Hier sei auch auf einen bereits existierenden Wikipedia-Artikel zu [http://de.wikipedia.org/wiki/Suchverfahren Suchverfahren] verwiesen.&lt;br /&gt;
&lt;br /&gt;
Allen gemeinsam ist die grundlegende Aufgabe, ein Datenelement mit bestimmten Eigenschaften aus einer großen Menge von Datenelementen zu selektieren.&lt;br /&gt;
Dies kann, natürlich ohne jeden Anspruch auf Vollständigkeit, nach einer der jetzt diskutierten Methoden geschehen:&lt;br /&gt;
&lt;br /&gt;
* '''Schlüsselsuche''': meint das Suchen von Elementen mit bestimmtem Schlüssel; ein klassisches Beispiel wäre das Suchen in einem Wörterbuch,  die Schlüssel entsprechen hier den Wörtern, die Datensätze wären die zu den Wörtern gehörigen Eintragungen.&lt;br /&gt;
&lt;br /&gt;
* '''Bereichssuche''': Im Allgemeinen meint die Bereichssuche in n-Dimensionen die Selektion von Elementen mit Eigenschaften aus einem bestimmten n-dimensionalen Volumen. Im eindimensionalen Fall will man alle Elemente finden, deren Eigenschaft(en) in einem bestimmten Intervall liegen. Die Verallgemeinerung auf n-Dimensionen ist offensichtlich. Ein Beispiel für die Bereichssuche in einer 3D-Kugel wäre ein Handy mit Geolokalisierung, welches alle Restaurants in einem Umkreis von 500m findet. Lineare Ungleichungen werden graphisch durch [http://de.wikipedia.org/wiki/Hyperebene Hyperebenen] repräsentiert. In 2D sind diese Hyperebenen Geraden. Die Ungleichungen können dann den Lösungsraum in irgendeiner Form begrenzen.&lt;br /&gt;
&lt;br /&gt;
* '''Ähnlichkeitssuche''': Finde Elemente, die gegebenen Eigenschaften möglichst ähnlich sind. Ein prominentes Beispiel ist Google (=Ähnlichkeit zwischen Suchbegriffen und Dokumenten) oder das Suchen des nächstengelegenen Restaurants (Ähnlichkeit zwischen eigener Position und Position des Restaurants). Ein wichtigster Spezialfall ist die ''nächste-nachbar Suche''.&lt;br /&gt;
&lt;br /&gt;
* '''Graphensuche''': Hier wäre beispielsweise das Problem optimaler Wege zu nennen (Navigationssuche). Dieser Punkt wird später im Verlauf der Vorlesung noch einmal aufgegriffen werden.&lt;br /&gt;
&lt;br /&gt;
Im jetzt Folgenden wird nur noch die ''Schlüsselsuche'' betrachtet werden.&lt;br /&gt;
&lt;br /&gt;
==Sequentielle Suche==&lt;br /&gt;
&lt;br /&gt;
Die ''sequentielle'' oder ''lineare'' Suche ist die 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 = sequentialSearch(a, key) &lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq &amp;lt;/math&amp;gt; foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def sequentialSearch(a, key):&lt;br /&gt;
    for i in range(len(a)):&lt;br /&gt;
        if a[i] == key:  # bzw. allgemeiner a[i].key == key &lt;br /&gt;
            return i&lt;br /&gt;
    return -1&lt;br /&gt;
&lt;br /&gt;
Wir wollen jetzt die Komplexität dieses Algorithmus bestimmen, wobei die Problemgröße durch &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt; gegeben ist. &lt;br /&gt;
&lt;br /&gt;
Dabei nimmt man an, dass der innerste Vergleich (a[i] == key) jeweils &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt; ist (diese Annahme könnte verletzt sein, wenn der Vergleichsoperator überladen ist und dadurch eine höhere Komplexität hat). Dieser Vergleich wird in der for-Schleife jeweils N-mal durchgeführt (&amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt;), so dass man nach der Verschachtelungsregel eine gesamte Komplexität von &amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt; erhält.&lt;br /&gt;
&lt;br /&gt;
Der Name ''lineare'' Suche rührt von diesem linearen Anwachsen der Komplexität mit der Arraygröße her.&lt;br /&gt;
&lt;br /&gt;
==Binäre Suche==&lt;br /&gt;
&lt;br /&gt;
Wie wir weiter unten zeigen werden, gestattet es diese Suchmethode, die Gesamtdauer der Suche in großen Datensätzen beträchtlich zu verringern. Die Methode beruht auf dem [http://de.wikipedia.org/wiki/Divide_and_Conquer Divide and Conquer-Prinzip], wobei die Suche in jedem Schritt rekursiv auf eine Hälfte des Datensatzes eingeschränkt wird. Weitere Details zur Methode sind [http://de.wikipedia.org/wiki/Bin%C3%A4re_Suche hier] zu finden. &lt;br /&gt;
&lt;br /&gt;
Die Methode ist nur dann anwendbar beziehungsweise effektiv, wenn folgendes gilt:&lt;br /&gt;
&lt;br /&gt;
# Auf der Eigenschaft der Daten, die zur Suche verwendet wird, ist eine Ordnung im Sinne von &amp;lt; oder &amp;gt; definiert.&lt;br /&gt;
# Wir wollen uns auf Datensätze beschränken, die schon fertig aufgebaut sind, in die also keine neuen Elemente mehr eingefügt werden, wenn man mit dem Suchen beginnt. Ist dies nicht der Fall, so ist unter Umständen die Implementierung über einen [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum Binärbaum] (siehe auch weiter unten) geschickter. &lt;br /&gt;
&lt;br /&gt;
Der folgende Algorithmus zeigt eine beispielhafte Implementierung der Methode (wir setzen wieder &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt;):&lt;br /&gt;
&lt;br /&gt;
 a = [...,...]     # array&lt;br /&gt;
 a.sort()   # sortiere über Ordnung des Schlüssels&lt;br /&gt;
 foundIndex = binSearch(a, key, 0, len(a))  # (Array, Schlüssel, von wo bis wo suchen im Array)&lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq&amp;lt;/math&amp;gt;  foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def binSearch(a, key, start, end):  # start ist 1. Index, end ist letzter Index + 1&lt;br /&gt;
    size = end - start   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if size &amp;lt;= 0:   # Bereich leer?  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return -1   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    center = (start + end)/2   # Integer Division, Ergebnis wird abgerundet, wichtig für ganzzahlige Indizes &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if a[center] == key:  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return center  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    elif a[center] &amp;lt; key:  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return binSearch(a, key, center + 1, end)  # Rekursion&lt;br /&gt;
    else:&lt;br /&gt;
        return binSearch(a, key, start + 1, center)  # Rekursion&lt;br /&gt;
&lt;br /&gt;
Zur Berechnung der Komplexität dieses Algorithmus vernachlässigen wir zunächst den Aufwand, den die Sortierung weiter oben verursacht. Dieser Schritt mag oder mag nicht zulässig sein.&lt;br /&gt;
&lt;br /&gt;
Nach der Sequenzregel haben auch alle &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;.  Es bleibt die Komplexität der Rekursion zu berechnen. Die gesamte Komplexität des Algorithmus (jetzt als Funktion f bezeichnet) setzt sich zusammen aus den oben erwähnten &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen sowie der Rekursion und ist &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f(N) = \mathcal{O}(1) + f(N/2) = \mathcal{O}(1) + \mathcal{O}(1) + f(N/4) = ... = \underbrace{\mathcal{O}(1) + ... + \mathcal{O}(1) + \underbrace{f(0)}_{\mathcal{O}(1)\, \rightarrow \,\mathrm{size-Abfrage}}}_{n+1 \,\mathrm{Terme}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Falls jetzt gilt &amp;lt;math&amp;gt; N = 2^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \rightarrow f(N) = \mathcal{O}(1) \cdot \mathcal{O}(n+1) = \mathcal{O}(n) = \mathcal{O}(\lg N) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Für große Datenmengen ist die ''binäre Suche'' also weit effizienter als die ''lineare Suche''. Verdoppelt sich beispielsweise die zu durchsuchende Datenmenge, so verdoppelt sich der Aufwand für die ''sequentielle Suche'' - bei der ''binären Suche'' hingegen benötigt man lediglich eine zusätzliche Vergleichsoperation. &lt;br /&gt;
&lt;br /&gt;
Für kleine Daten (&amp;lt;math&amp;gt; N = 4,\, 5 &amp;lt;/math&amp;gt;) ist die ''sequentielle Suche'' jedoch schneller als die ''binäre Suche'', da hier die rekursiven Funktionsaufrufe teurer als das Mehr an Vergleichen sind. Ein anderer ungünstiger Fall ist gegeben, wenn nur sehr wenige Suchanfragen erfolgen (weniger als &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; viele). Dann wird der Aufwand durch das Sortieren des Arrays dominiert, ist also &amp;lt;math&amp;gt;\mathcal{O}(N \lg N) &amp;lt;/math&amp;gt;. Auch dann ist sequentielle Suche vorzuziehen.&lt;br /&gt;
&lt;br /&gt;
Eine relativ einfache Möglichkeit, die ''binäre Suche'' zu verbessern, ist die sogenannte ''Interpolationssuche''. Hierbei wird die neue Position für die Suche, also die Mitte des Arrays, durch eine Schätzung ersetzt, die angibt, wo sich der Schlüssel innerhalb des Arrays befinden könnte. Bei der Suche in einem Telefonbuch nach dem Namen Zebra würde man ja auch nicht in der Mitte anfangen. Näheres hierzu im Buch von ''Sedgewick''.&lt;br /&gt;
&lt;br /&gt;
Um sich den Algorithmus der ''binären Suche'' klar zu machen, ist es instruktiv, sich die folgende Tabelle genauer anzusehen, die die sukzessive Belegung der Variablen bei verschiedenen Anfragen beschreibt. Die Testfälle wurden nach dem Prinzip des ''domain partitioning'' gewählt. Das zugehörige Array hat die Einträge&lt;br /&gt;
&lt;br /&gt;
 a = [2, 3, 4, 5, 6]&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align:center&amp;quot; border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;5&amp;quot; cellspacing=&amp;quot;0&amp;quot; &lt;br /&gt;
! gesuchter key   !!  start      !! end  !! size !! center !! return &amp;lt;br/&amp;gt; (-1 oder index)  !! Kommentare  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 4     ||0            || 5    ||  5   || 2   ||  2         || gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 2     || 0           || 5    ||  5   || 2   ||            || linker Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||           &lt;br /&gt;
|-&lt;br /&gt;
|       ||  0          || 1    ||  1   || 0   ||  0         || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 1     ||0            || 5    ||  5   || 2   ||            || links außerhalb&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 1    ||  1   || 0   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 0    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 6     ||0            || 5    ||  5   || 2   ||            || rechter Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    || 2    || 4   || 4          || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 5     ||0            || 5    ||  5   || 2   ||            || typischer Fall&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||3            || 5    ||  2   || 4   ||            ||  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       || 3           || 4    ||  1   || 3   || 3          || gefunden&lt;br /&gt;
|- &lt;br /&gt;
| 7     ||0            || 5    ||  5   || 2   ||            || rechts außerhalb        &lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    ||  2   || 4   ||            ||&lt;br /&gt;
|-&lt;br /&gt;
|       ||5            || 5    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Suche in einem Binärbaum ==&lt;br /&gt;
&lt;br /&gt;
Eine kurze Einführung in Binärbäume findet man [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum hier].&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum.png|text-top|300x300px|Zur Illustration von Bäumen]]&lt;br /&gt;
&lt;br /&gt;
Bäume sind zweidimensional verkettete Strukturen. Sie gehören zu den fundamentalen Datenstrukturen in der Informatik. Da man in Bäumen nicht nur Daten speichern kann, sondern auch relevante Beziehungen der Daten untereinander, festgelegt über eine Ordnung auf der vergleichenden Dateneigenschaft (''Schlüssel''), eignen sich Bäume also insbesondere, um gesuchte Daten schnell wieder auffinden zu können.&lt;br /&gt;
&lt;br /&gt;
Ein ''Binärbaum'' wie oben skizziert besteht aus einer Menge von ''Knoten'', die untereinander durch ''Kanten'' verbunden sind. Jeder Knoten hat einen linken und einen rechten Unterbaum, der auch leer sein kann (in Python ließe sich dies mit ''None'' implementieren). Führt eine Kante von Knoten A zu Knoten B, so heißt A Vater von B und B Kind von A. Es gibt genau einen Knoten ohne Vater, den man ''Wurzel'' nennt. Knoten ohne Kinder heißen ''Blätter''.&lt;br /&gt;
&lt;br /&gt;
Ein ''Suchbaum'' hat zusätzlich die Eigenschaft, dass die Schlüssel jedes Knotens sortiert sind. Alle Schlüssel im linken Unterbaum sind kleiner, alle Schlüssel im rechten Unterbaum sind größer als ihr Vater. Wir wollen hierbei annehmen, dass jeder Schlüssel pro Datensatz nur einmal vorkommt, da sich sonst die &amp;gt;- oder &amp;lt;-Relation nicht mehr strikt erfüllen ließe.&lt;br /&gt;
&lt;br /&gt;
Um in einem Baum suchen zu können, wollen wir von zwei Annahmen ausgehen:&lt;br /&gt;
# Einfügen und Suchen im Baum wechseln sich ab. (Wenn das Suchen erst beginnt, nachdem alle Einfügungen erfolgt sind, wäre ein dynamisches Array mit binärer Suche wie oben wesentlich einfacher.)&lt;br /&gt;
# Der Schlüssel, der die Anordnung bestimmt, kennt eine [http://de.wikipedia.org/wiki/Ordnungsrelation Ordnung] (&amp;lt;-Relation oder &amp;gt;-Relation).&lt;br /&gt;
&lt;br /&gt;
Der folgende Python-Code zeigt beispielhaft, wie man in einem Suchbaum suchen könnte. Der Konstruktor für einen Knoten des Suchbaums ließe sich zum Beispiel so implementieren:&lt;br /&gt;
 &lt;br /&gt;
 class Node:&lt;br /&gt;
     def __init__(self, key):&lt;br /&gt;
         self.key = key&lt;br /&gt;
         self.left = self.right = None&lt;br /&gt;
 &lt;br /&gt;
 root = ...    # Wurzel des Suchbaums&lt;br /&gt;
 nodeFound = treeSearch(root, key)   # None, falls nichts gefunden&lt;br /&gt;
 &lt;br /&gt;
 def treeSearch(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return None&lt;br /&gt;
     elif node.key == key:&lt;br /&gt;
         return node&lt;br /&gt;
     elif key &amp;lt; node.key:&lt;br /&gt;
         return treeSearch(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         return treeSearch(node.right, key)&lt;br /&gt;
&lt;br /&gt;
Daraus resultiert der folgende Suchalgorithmus:&lt;br /&gt;
&lt;br /&gt;
 def treeSort(node,array):     # dynamisches Array als 2. Argument&lt;br /&gt;
     if node is None:     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
         return None:&lt;br /&gt;
     treeSort(node.left, array)     # rekursiv&lt;br /&gt;
     array.append(node.key)     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
     treeSort(node.right, array)     # rekursiv&lt;br /&gt;
&lt;br /&gt;
Komplexität:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 f(N)=\mathcal{O}(1)+f(N_\mathrm{left})+f(N_\mathrm{right})=\mathcal{O}(1)+\mathcal{O}(1)+f(N_\mathrm{leftleft})+f(N_\mathrm{leftright})+\mathcal{O}(1)+f(N_\mathrm{rightleft})&lt;br /&gt;
 +f(N_\mathrm{leftright})=N\ast\mathcal{O}(1)=\mathcal{O}(N)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sortier-Pseudocode:&lt;br /&gt;
&lt;br /&gt;
 Sortieren:&lt;br /&gt;
     (Array) a     # unsortiert&lt;br /&gt;
     (tree) t     # zunächst leer&lt;br /&gt;
 (dynamisches Array) r     # später sortiert&lt;br /&gt;
 for e in a:&lt;br /&gt;
     t = treeInsert(t, e)&lt;br /&gt;
 treeSort(t, r)&lt;br /&gt;
&lt;br /&gt;
== Insert ==&lt;br /&gt;
&lt;br /&gt;
Was passiert wenn der key (Schlüssel) schon vorhanden ist?&lt;br /&gt;
* error (exception)&lt;br /&gt;
* nichts einfügen&lt;br /&gt;
* nichts einfügen aber einen boolean return zurückgeben (einfügen=true, false)&lt;br /&gt;
* nochmals eingefügt (z.B. in der Node Klasse)&lt;br /&gt;
&lt;br /&gt;
Wobei die ersten 3 Punkte zur Mengensemantik gehören und der letzte eine Multimenge ist.&lt;br /&gt;
&lt;br /&gt;
Algorithmus im zweiten Fall (nichts einfügen):&lt;br /&gt;
&lt;br /&gt;
 def treeInsert(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return Node(key)     # Alternative Schreibweise: node = Node(key)&lt;br /&gt;
     if node.key == key:      # und dann:&lt;br /&gt;
         return node          # pass&lt;br /&gt;
     elif key &amp;lt; node.key:     # links im Baum&lt;br /&gt;
         node.left = treeInsert(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         node.right = treeInsert(node.right, key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Remove ==&lt;br /&gt;
Fälle:&lt;br /&gt;
# key (bzw. Knoten der key enthält) ist ein Blatt =&amp;gt; einfach löschen&lt;br /&gt;
# node hat &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; linken Unterbaum oder &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; rechten Unterbaum =&amp;gt; durch Unterbaum ersetzen&lt;br /&gt;
# node hat beide Unterbäume:&lt;br /&gt;
#* Suche Vorgänger: max k &amp;lt; key (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; keys); Vorgänger ist immer ein Fall 1 oder Fall 2&lt;br /&gt;
#*:=&amp;gt; ersetze node durch Vorgänger und entferne Vorgänger&lt;br /&gt;
&lt;br /&gt;
 def treePredecessor(node):     # wird nur bei Fall 3 aufgerufen&lt;br /&gt;
     node = node.left&lt;br /&gt;
     while node.right is not None:&lt;br /&gt;
         node = node.right&lt;br /&gt;
     return node&lt;br /&gt;
 &lt;br /&gt;
 def treeRemove(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return node&lt;br /&gt;
     if key &amp;lt; node.key:&lt;br /&gt;
         node.left = treeRemove(node.left, key)&lt;br /&gt;
     elif key &amp;gt; node.key:&lt;br /&gt;
         node.right = treeRemove(node.right, key)&lt;br /&gt;
     else:&lt;br /&gt;
         if node.left is None and node.right is None:     # Fall 1&lt;br /&gt;
             node = None&lt;br /&gt;
         elif node.left is None:     # Fall 2&lt;br /&gt;
             node = node.right       # +&lt;br /&gt;
         elif node.right is None:    # Fall 2&lt;br /&gt;
             node = node.left&lt;br /&gt;
         else:&lt;br /&gt;
             pred = treePredecessor(node)&lt;br /&gt;
             node.key = pred.key&lt;br /&gt;
             node.left = treeRemove(node.left, pred.key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Komplexitätsanalyse ==&lt;br /&gt;
&lt;br /&gt;
* Pfad (Zwischen node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;): &lt;br /&gt;
**Folge von Knoten (node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt;,...,node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt;), sodass:&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;ki&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;ki+1&amp;lt;/sub&amp;gt; haben eine gemeinsame Kante.&lt;br /&gt;
[[Image:Baum_Pfad.png]]&lt;br /&gt;
(Ein Baum ist ein Graph, indem es zwischen beliebigen Knoten stets genau einen Pfad gibt.)&lt;br /&gt;
&lt;br /&gt;
* Länge eines Pfades: Anzahl der Kanten = Anzahl der Knoten - 1&lt;br /&gt;
* Tiefe eines Knotens: Pfadlänge vom Knoten zur Wurzel des Baumes.&lt;br /&gt;
* Tiefe des Baumes: maximale Tiefe eines Knotens&lt;br /&gt;
* Balance eines Baumes: maximale Tiefe(k) - minimale Tiefe(k) (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; Blätter)&lt;br /&gt;
&lt;br /&gt;
== Ungünstigster Fall von treeSearch ==&lt;br /&gt;
&lt;br /&gt;
Komplexität von treeSearch = Länge des Pfades zum Knoten wo &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; gefunden wird, oder erkannt wird, dass &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; nicht im Baum ist.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; wird nicht gefunden, aber für diese Entscheidung muss der Längste Pfad vollständig durchlaufen werden.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(T)&amp;lt;/math&amp;gt; wo T = Tiefe des Baumes&lt;br /&gt;
*: [1,2,3,4,5]:&amp;lt;br /&amp;gt; [[Image:Balance.png]]&lt;br /&gt;
*: Tiefe T = 4, Balance = 4&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; wo N = Anzahl der Elemente.&lt;br /&gt;
&lt;br /&gt;
== Aufgabe ==&lt;br /&gt;
&lt;br /&gt;
Minimiere Balance (erzeuge balancierten Baum):&lt;br /&gt;
# Einfügen in geschickter Reihenfolge (siehe Übungsaufgabe)&lt;br /&gt;
# Selbstbalancierter Baum:&lt;br /&gt;
#* Überprüfen der Balance nach jedem Einfügen&lt;br /&gt;
#* Umstrukturieren des Baumes, falls Balance &amp;gt; 1 (Suchbaum-Bedingung muss erhalten bleiben)&lt;br /&gt;
#* AVL-Bäume (älteste Variante)&lt;br /&gt;
#* Rot-Schwarz-Bäume (verbreiteste Variante)&lt;br /&gt;
#* Treaps (flexibelste Variante, siehe Übung)&lt;br /&gt;
#* Splay trees&lt;br /&gt;
#* Andersson Trees (einfachste Variante)&lt;br /&gt;
(#* Skip Lists (schnellste Variante, aber kein Binärbaum))&lt;br /&gt;
&lt;br /&gt;
== Umstrukturieren, so dass Suchbaumbedingung erhalten bleibt: ==&lt;br /&gt;
&lt;br /&gt;
Rotation: elementare Umstrukturierungen&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum_Rotation.png]]&lt;br /&gt;
&lt;br /&gt;
 def rotateRight(node):&lt;br /&gt;
     newRoot = node.left&lt;br /&gt;
     node.left = newRoot.right&lt;br /&gt;
     newRoot.right = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
 def rotateLeft(node):&lt;br /&gt;
     newRoot = node.right&lt;br /&gt;
     node.right = newRoot.left&lt;br /&gt;
     newRoot.left = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
== Balance eines Suchbaumes ==&lt;br /&gt;
&lt;br /&gt;
===Balance eines Baumes zu  definieren:===&lt;br /&gt;
[[Image:Abbildung1.jpg|250px|right]]&lt;br /&gt;
*betrachte &amp;lt;tt&amp;gt;None&amp;lt;/tt&amp;gt; als zusätzlichen Knoten : '''sentinel'''   ([[englische Sprache|engl.]] für ''Wächter'')&lt;br /&gt;
*oder defeniere speziellen sentinel-Knoten&lt;br /&gt;
*RS - Pfades : von &amp;lt;tt&amp;gt;root&amp;lt;/tt&amp;gt; &amp;amp;rarr; ''sentinel''&lt;br /&gt;
*Länge eines Pfades ''P''      '''''|P|'''''&lt;br /&gt;
* &amp;lt;u&amp;gt;die Balance eines Baumes zu defenieren:&amp;lt;/u&amp;gt;&lt;br /&gt;
: '''''B = max P '''''&amp;lt;math&amp;gt; \in &amp;lt;/math&amp;gt; '''''{RS}    |P| - min |P|'''''&lt;br /&gt;
:'''''{RS}'''''  Menge aller RS-Pfade&lt;br /&gt;
;vollständiger Baum:  Balance &amp;lt;math&amp;gt;~=0&amp;lt;/math&amp;gt;&lt;br /&gt;
:Alle Knoten(außer Blättern) haben 2 Kinder.&lt;br /&gt;
;perfect balancierter Baum:  Balance  &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 1&lt;br /&gt;
'''alternative Defenition für perfect balancierte Bäume'''&lt;br /&gt;
:Für jeden Teilbaum gilt es: rechtes und linkes Kind (für jeden Knoten) sind auch wieder perfect balancierte Bäume und ihre Höhe unterscheidet sich höchstens um '''1'''.&lt;br /&gt;
&lt;br /&gt;
===Größe===&lt;br /&gt;
[[Image:baum-v.png|700px|right]]&lt;br /&gt;
====Größe des vollständigen Baumes====&lt;br /&gt;
Ebene K hat 2 &amp;lt;sup&amp;gt;k&amp;lt;/sup&amp;gt; Knoten.&lt;br /&gt;
Falls Tiefe = d , dann N = 2&amp;lt;sup&amp;gt;0&amp;lt;/sup&amp;gt; + 2&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;.....+ 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; = 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1&lt;br /&gt;
&lt;br /&gt;
====Größe des perfect balancierten Baumes====&lt;br /&gt;
Die Tiefe d, kann nicht besser sein als vollständiger Baum&lt;br /&gt;
:'''=&amp;gt;'''N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2 &amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
;schlechteste perfect balancierter Baum : ist ein vollständiger der Tiefe  d - 1 + 1, N &amp;lt;math&amp;gt; \ge&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:'''=&amp;gt;'''  2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1)   &amp;lt;math&amp;gt;~&amp;lt;&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt;)&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
d &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt;  log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; (d + 1)&lt;br /&gt;
&lt;br /&gt;
*Ergibt die Komplexität der Suche im schlechtesten Fall: Anzahl der Vergleiche pro Knoten( = 2 bzw. = 3)&amp;lt;math&amp;gt;\ast&amp;lt;/math&amp;gt;Anzahl der Knoten&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow ~f(N)\le 2d \le 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
*'''perfekt balancierter Baum '''=&amp;gt; AVL-Baum &lt;br /&gt;
*'''balancierter Baum''':&lt;br /&gt;
::&amp;lt;math&amp;gt;\forall  N:d(N)\le c \ast d (N)&amp;lt;/math&amp;gt; und  &amp;lt;math&amp;gt;1 \le c &amp;lt; \mathcal {1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:: d ist die Tiefe von perfekt balancierten Baum&lt;br /&gt;
&lt;br /&gt;
:''Komplexität der Suche'':&amp;lt;math&amp;gt; ~f(N)\le  c\ast 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
:algorithmisch einfacher als perfekt balancierter Baum, aber fast genauso schnell&lt;br /&gt;
&lt;br /&gt;
====Selbst-balancierter Baum====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  def insertTree(node,key):&lt;br /&gt;
    if node is sentinel:       #(None = sentinel)&lt;br /&gt;
        return Node(key)       &lt;br /&gt;
    if node.key == key:&lt;br /&gt;
        return Node&lt;br /&gt;
    if key &amp;lt; node.key:&lt;br /&gt;
        Node = insertTree(node.left, key)&lt;br /&gt;
    else:&lt;br /&gt;
        node.right = insertTree(node.right, key)&lt;br /&gt;
   #optimiere Balance:&lt;br /&gt;
        return node&lt;br /&gt;
&lt;br /&gt;
===Anderson-Bäume===&lt;br /&gt;
&lt;br /&gt;
  class Node:&lt;br /&gt;
    def__init__(self, key):&lt;br /&gt;
        self.key = key&lt;br /&gt;
        self.left = selft.right = None&lt;br /&gt;
        #einfügen:&lt;br /&gt;
        self.level = 1&lt;br /&gt;
&lt;br /&gt;
:''level'' : kodiert Abstand von sentinel&lt;br /&gt;
====Regeln====&lt;br /&gt;
Es gibt vertikale Kanten(parent.level == child.level + 1 ) und horizontale Kanten(parent.level == child.level)&lt;br /&gt;
:*verfeinerter Regel :  ''level kodiert reduzierten Abstand von Setinel''  (d.b. horizontale Kanten werden nicht gezählt)&lt;br /&gt;
::(die nächste zwei Regeln sichern die Balance):&lt;br /&gt;
::*alle RS-Pfade haben die gleiche reduzierte Länge oder ''root'' hat bei allen Pfaden das gleiche Level&lt;br /&gt;
::*kein Pfad hat 2 aufeinanderer folgende horizontale Kanten&lt;br /&gt;
:*nur Kanten zum rechten Kind dürfen horizontal sein&lt;br /&gt;
:*die reduzierte Höhe jedes Blatts ist '''hr=1'''&lt;br /&gt;
&lt;br /&gt;
====Beweis====&lt;br /&gt;
Vereinfachung des Algorithmus&lt;br /&gt;
:'''Satz''':ein Anderson-Baum ist balanciert.&lt;br /&gt;
*1. Sei '''hr'''- die reduzierte Höhe&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; jeder Teilbaum enthält mindestens &amp;lt;math&amp;gt;N\ge 2^{hr} -1 &amp;lt;/math&amp;gt;  Knoten&lt;br /&gt;
&lt;br /&gt;
::a). Blätter : reduzierte Höhe 1 =&amp;gt; &amp;lt;math&amp;gt; N\ge 2^{1} - 1 = 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
::b). inneren Knoten: jeder Unterbaum hat mindestens reduzierte Höhe &amp;lt;math&amp;gt; ~hr - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt;  jeder Unterbaum hat mindestens &amp;lt;math&amp;gt; ~2^{hr} -1 &amp;lt;/math&amp;gt; Knoten&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt;N \ge 2 (2^{hr-1} -1)+1 = 2^w - 2 + 1 = 2^{hr} -1&amp;lt;/math&amp;gt;&lt;br /&gt;
::::alle RS-Pfade heben gleiche Länge&lt;br /&gt;
[[Image:Anderson.png|700px|right]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
*2. Kein Pfad hat 2 aufeinanderfolgende horizontal Kanten&lt;br /&gt;
:::&amp;lt;math&amp;gt;\Rightarrow  d \le 2 hr + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
*3. zusammen:&lt;br /&gt;
:&amp;lt;math&amp;gt;N \ge 2^{\frac {d}{2}} - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;log_{2}{ N}+1\ge log 2^{\frac {d}{2}} - 1 \ge \frac {d}{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; ~d &amp;lt; 2 log_{2}{(N + 1)}&amp;lt;/math&amp;gt;     - balancierter Baum&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
:'''Suchzeit''' : &amp;lt;math&amp;gt; ~ f(N) = \mathcal{O}(log{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Wie erreicht man die Balance===&lt;br /&gt;
====Rotation====&lt;br /&gt;
[[Image:Abbildung4.jpg|text-top|350px|Illustration von Rotation]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
   def rotateRight (node):&lt;br /&gt;
      root = node.left&lt;br /&gt;
      node.left = node.right&lt;br /&gt;
      root.right = node&lt;br /&gt;
      return root&lt;br /&gt;
   def rotateLeft(node):&lt;br /&gt;
      root = node.right&lt;br /&gt;
      node.right = node.left&lt;br /&gt;
      root.left = node&lt;br /&gt;
      return root&lt;br /&gt;
&lt;br /&gt;
====Optimierung der Balance====&lt;br /&gt;
   &lt;br /&gt;
&lt;br /&gt;
       if node.left is not sentinel and node.level==node.left.level:&lt;br /&gt;
            node &amp;gt; rotateRight(node)&lt;br /&gt;
       if node.right is not sentinel and node.right.right is  not sentinel and node.level==rotate.right.right.level:&lt;br /&gt;
            node = rotateLeft(node)&lt;br /&gt;
            node.level += 1&lt;/div&gt;</summary>
		<author><name>Nata</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1494</id>
		<title>Suchen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1494"/>
				<updated>2008-05-29T00:17:16Z</updated>
		
		<summary type="html">&lt;p&gt;Nata: /* Rotation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Es wäre super wenn jemand ganz kurz schreiben könnte, was am Donnerstag zu den Funktionen treeInsert/Remove/HasKey gemacht wurde, was man ja für den aktuellen Übungszettel braucht. Danke :-)&amp;lt;br /&amp;gt;&lt;br /&gt;
/edit: an eine Funktion &amp;quot;HasKey&amp;quot; kann ich mich aus der Vorlesung nicht erinnern. Wenn die jemand hat, bitte eintragen. - Protokollant&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--Hallo, ich kenne leider deinen Namen nicht, ich habe den Wiki-Eintrag von letztem Donnerstag gemacht.Da das Thema 'Dynamisches Array' noch Thema von letzter Woche war, hab ich die Wiederholung der amortisierten Kosten von diesem Mittwoch noch bei Effizienz eingetragen, du musst es also nicht noch mal in deinem Wiki eintragen. Du kannst dir aber gerne mal anschauen was ich geschrieben habe, vielleicht fallen dir noch ein paar Sachen ein, die man hätte besser machen können. lg, Franziska.--&amp;gt;&lt;br /&gt;
Das Suchen ist eine grundlegende Operation in der Informatik. Viele Probleme in der Informatik können auf Suchaufgaben zurückgeführt werden.&lt;br /&gt;
&lt;br /&gt;
Gemeint ist mit Suchen das Wiederauffinden einer oder mehrerer Datensätze aus einer Menge von früher gespeicherten Datensätzen. Ein paar einleitende Worte zum Suchproblem findet man [http://de.wikipedia.org/wiki/Suche hier].&lt;br /&gt;
&lt;br /&gt;
== Überblick verschiedener Suchmethoden ==&lt;br /&gt;
&lt;br /&gt;
Um sich der Vielseitigkeit von Suchproblemen bewusst zu werden, ist es sinnvoll, sich einen Überblick über verschiedene Suchmethoden zu verschaffen. &lt;br /&gt;
&lt;br /&gt;
Hier sei auch auf einen bereits existierenden Wikipedia-Artikel zu [http://de.wikipedia.org/wiki/Suchverfahren Suchverfahren] verwiesen.&lt;br /&gt;
&lt;br /&gt;
Allen gemeinsam ist die grundlegende Aufgabe, ein Datenelement mit bestimmten Eigenschaften aus einer großen Menge von Datenelementen zu selektieren.&lt;br /&gt;
Dies kann, natürlich ohne jeden Anspruch auf Vollständigkeit, nach einer der jetzt diskutierten Methoden geschehen:&lt;br /&gt;
&lt;br /&gt;
* '''Schlüsselsuche''': meint das Suchen von Elementen mit bestimmtem Schlüssel; ein klassisches Beispiel wäre das Suchen in einem Wörterbuch,  die Schlüssel entsprechen hier den Wörtern, die Datensätze wären die zu den Wörtern gehörigen Eintragungen.&lt;br /&gt;
&lt;br /&gt;
* '''Bereichssuche''': Im Allgemeinen meint die Bereichssuche in n-Dimensionen die Selektion von Elementen mit Eigenschaften aus einem bestimmten n-dimensionalen Volumen. Im eindimensionalen Fall will man alle Elemente finden, deren Eigenschaft(en) in einem bestimmten Intervall liegen. Die Verallgemeinerung auf n-Dimensionen ist offensichtlich. Ein Beispiel für die Bereichssuche in einer 3D-Kugel wäre ein Handy mit Geolokalisierung, welches alle Restaurants in einem Umkreis von 500m findet. Lineare Ungleichungen werden graphisch durch [http://de.wikipedia.org/wiki/Hyperebene Hyperebenen] repräsentiert. In 2D sind diese Hyperebenen Geraden. Die Ungleichungen können dann den Lösungsraum in irgendeiner Form begrenzen.&lt;br /&gt;
&lt;br /&gt;
* '''Ähnlichkeitssuche''': Finde Elemente, die gegebenen Eigenschaften möglichst ähnlich sind. Ein prominentes Beispiel ist Google (=Ähnlichkeit zwischen Suchbegriffen und Dokumenten) oder das Suchen des nächstengelegenen Restaurants (Ähnlichkeit zwischen eigener Position und Position des Restaurants). Ein wichtigster Spezialfall ist die ''nächste-nachbar Suche''.&lt;br /&gt;
&lt;br /&gt;
* '''Graphensuche''': Hier wäre beispielsweise das Problem optimaler Wege zu nennen (Navigationssuche). Dieser Punkt wird später im Verlauf der Vorlesung noch einmal aufgegriffen werden.&lt;br /&gt;
&lt;br /&gt;
Im jetzt Folgenden wird nur noch die ''Schlüsselsuche'' betrachtet werden.&lt;br /&gt;
&lt;br /&gt;
==Sequentielle Suche==&lt;br /&gt;
&lt;br /&gt;
Die ''sequentielle'' oder ''lineare'' Suche ist die 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 = sequentialSearch(a, key) &lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq &amp;lt;/math&amp;gt; foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def sequentialSearch(a, key):&lt;br /&gt;
    for i in range(len(a)):&lt;br /&gt;
        if a[i] == key:  # bzw. allgemeiner a[i].key == key &lt;br /&gt;
            return i&lt;br /&gt;
    return -1&lt;br /&gt;
&lt;br /&gt;
Wir wollen jetzt die Komplexität dieses Algorithmus bestimmen, wobei die Problemgröße durch &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt; gegeben ist. &lt;br /&gt;
&lt;br /&gt;
Dabei nimmt man an, dass der innerste Vergleich (a[i] == key) jeweils &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt; ist (diese Annahme könnte verletzt sein, wenn der Vergleichsoperator überladen ist und dadurch eine höhere Komplexität hat). Dieser Vergleich wird in der for-Schleife jeweils N-mal durchgeführt (&amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt;), so dass man nach der Verschachtelungsregel eine gesamte Komplexität von &amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt; erhält.&lt;br /&gt;
&lt;br /&gt;
Der Name ''lineare'' Suche rührt von diesem linearen Anwachsen der Komplexität mit der Arraygröße her.&lt;br /&gt;
&lt;br /&gt;
==Binäre Suche==&lt;br /&gt;
&lt;br /&gt;
Wie wir weiter unten zeigen werden, gestattet es diese Suchmethode, die Gesamtdauer der Suche in großen Datensätzen beträchtlich zu verringern. Die Methode beruht auf dem [http://de.wikipedia.org/wiki/Divide_and_Conquer Divide and Conquer-Prinzip], wobei die Suche in jedem Schritt rekursiv auf eine Hälfte des Datensatzes eingeschränkt wird. Weitere Details zur Methode sind [http://de.wikipedia.org/wiki/Bin%C3%A4re_Suche hier] zu finden. &lt;br /&gt;
&lt;br /&gt;
Die Methode ist nur dann anwendbar beziehungsweise effektiv, wenn folgendes gilt:&lt;br /&gt;
&lt;br /&gt;
# Auf der Eigenschaft der Daten, die zur Suche verwendet wird, ist eine Ordnung im Sinne von &amp;lt; oder &amp;gt; definiert.&lt;br /&gt;
# Wir wollen uns auf Datensätze beschränken, die schon fertig aufgebaut sind, in die also keine neuen Elemente mehr eingefügt werden, wenn man mit dem Suchen beginnt. Ist dies nicht der Fall, so ist unter Umständen die Implementierung über einen [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum Binärbaum] (siehe auch weiter unten) geschickter. &lt;br /&gt;
&lt;br /&gt;
Der folgende Algorithmus zeigt eine beispielhafte Implementierung der Methode (wir setzen wieder &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt;):&lt;br /&gt;
&lt;br /&gt;
 a = [...,...]     # array&lt;br /&gt;
 a.sort()   # sortiere über Ordnung des Schlüssels&lt;br /&gt;
 foundIndex = binSearch(a, key, 0, len(a))  # (Array, Schlüssel, von wo bis wo suchen im Array)&lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq&amp;lt;/math&amp;gt;  foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def binSearch(a, key, start, end):  # start ist 1. Index, end ist letzter Index + 1&lt;br /&gt;
    size = end - start   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if size &amp;lt;= 0:   # Bereich leer?  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return -1   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    center = (start + end)/2   # Integer Division, Ergebnis wird abgerundet, wichtig für ganzzahlige Indizes &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if a[center] == key:  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return center  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    elif a[center] &amp;lt; key:  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return binSearch(a, key, center + 1, end)  # Rekursion&lt;br /&gt;
    else:&lt;br /&gt;
        return binSearch(a, key, start + 1, center)  # Rekursion&lt;br /&gt;
&lt;br /&gt;
Zur Berechnung der Komplexität dieses Algorithmus vernachlässigen wir zunächst den Aufwand, den die Sortierung weiter oben verursacht. Dieser Schritt mag oder mag nicht zulässig sein.&lt;br /&gt;
&lt;br /&gt;
Nach der Sequenzregel haben auch alle &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;.  Es bleibt die Komplexität der Rekursion zu berechnen. Die gesamte Komplexität des Algorithmus (jetzt als Funktion f bezeichnet) setzt sich zusammen aus den oben erwähnten &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen sowie der Rekursion und ist &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f(N) = \mathcal{O}(1) + f(N/2) = \mathcal{O}(1) + \mathcal{O}(1) + f(N/4) = ... = \underbrace{\mathcal{O}(1) + ... + \mathcal{O}(1) + \underbrace{f(0)}_{\mathcal{O}(1)\, \rightarrow \,\mathrm{size-Abfrage}}}_{n+1 \,\mathrm{Terme}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Falls jetzt gilt &amp;lt;math&amp;gt; N = 2^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \rightarrow f(N) = \mathcal{O}(1) \cdot \mathcal{O}(n+1) = \mathcal{O}(n) = \mathcal{O}(\lg N) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Für große Datenmengen ist die ''binäre Suche'' also weit effizienter als die ''lineare Suche''. Verdoppelt sich beispielsweise die zu durchsuchende Datenmenge, so verdoppelt sich der Aufwand für die ''sequentielle Suche'' - bei der ''binären Suche'' hingegen benötigt man lediglich eine zusätzliche Vergleichsoperation. &lt;br /&gt;
&lt;br /&gt;
Für kleine Daten (&amp;lt;math&amp;gt; N = 4,\, 5 &amp;lt;/math&amp;gt;) ist die ''sequentielle Suche'' jedoch schneller als die ''binäre Suche'', da hier die rekursiven Funktionsaufrufe teurer als das Mehr an Vergleichen sind. Ein anderer ungünstiger Fall ist gegeben, wenn nur sehr wenige Suchanfragen erfolgen (weniger als &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; viele). Dann wird der Aufwand durch das Sortieren des Arrays dominiert, ist also &amp;lt;math&amp;gt;\mathcal{O}(N \lg N) &amp;lt;/math&amp;gt;. Auch dann ist sequentielle Suche vorzuziehen.&lt;br /&gt;
&lt;br /&gt;
Eine relativ einfache Möglichkeit, die ''binäre Suche'' zu verbessern, ist die sogenannte ''Interpolationssuche''. Hierbei wird die neue Position für die Suche, also die Mitte des Arrays, durch eine Schätzung ersetzt, die angibt, wo sich der Schlüssel innerhalb des Arrays befinden könnte. Bei der Suche in einem Telefonbuch nach dem Namen Zebra würde man ja auch nicht in der Mitte anfangen. Näheres hierzu im Buch von ''Sedgewick''.&lt;br /&gt;
&lt;br /&gt;
Um sich den Algorithmus der ''binären Suche'' klar zu machen, ist es instruktiv, sich die folgende Tabelle genauer anzusehen, die die sukzessive Belegung der Variablen bei verschiedenen Anfragen beschreibt. Die Testfälle wurden nach dem Prinzip des ''domain partitioning'' gewählt. Das zugehörige Array hat die Einträge&lt;br /&gt;
&lt;br /&gt;
 a = [2, 3, 4, 5, 6]&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align:center&amp;quot; border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;5&amp;quot; cellspacing=&amp;quot;0&amp;quot; &lt;br /&gt;
! gesuchter key   !!  start      !! end  !! size !! center !! return &amp;lt;br/&amp;gt; (-1 oder index)  !! Kommentare  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 4     ||0            || 5    ||  5   || 2   ||  2         || gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 2     || 0           || 5    ||  5   || 2   ||            || linker Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||           &lt;br /&gt;
|-&lt;br /&gt;
|       ||  0          || 1    ||  1   || 0   ||  0         || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 1     ||0            || 5    ||  5   || 2   ||            || links außerhalb&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 1    ||  1   || 0   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 0    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 6     ||0            || 5    ||  5   || 2   ||            || rechter Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    || 2    || 4   || 4          || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 5     ||0            || 5    ||  5   || 2   ||            || typischer Fall&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||3            || 5    ||  2   || 4   ||            ||  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       || 3           || 4    ||  1   || 3   || 3          || gefunden&lt;br /&gt;
|- &lt;br /&gt;
| 7     ||0            || 5    ||  5   || 2   ||            || rechts außerhalb        &lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    ||  2   || 4   ||            ||&lt;br /&gt;
|-&lt;br /&gt;
|       ||5            || 5    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Suche in einem Binärbaum ==&lt;br /&gt;
&lt;br /&gt;
Eine kurze Einführung in Binärbäume findet man [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum hier].&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum.png|text-top|300x300px|Zur Illustration von Bäumen]]&lt;br /&gt;
&lt;br /&gt;
Bäume sind zweidimensional verkettete Strukturen. Sie gehören zu den fundamentalen Datenstrukturen in der Informatik. Da man in Bäumen nicht nur Daten speichern kann, sondern auch relevante Beziehungen der Daten untereinander, festgelegt über eine Ordnung auf der vergleichenden Dateneigenschaft (''Schlüssel''), eignen sich Bäume also insbesondere, um gesuchte Daten schnell wieder auffinden zu können.&lt;br /&gt;
&lt;br /&gt;
Ein ''Binärbaum'' wie oben skizziert besteht aus einer Menge von ''Knoten'', die untereinander durch ''Kanten'' verbunden sind. Jeder Knoten hat einen linken und einen rechten Unterbaum, der auch leer sein kann (in Python ließe sich dies mit ''None'' implementieren). Führt eine Kante von Knoten A zu Knoten B, so heißt A Vater von B und B Kind von A. Es gibt genau einen Knoten ohne Vater, den man ''Wurzel'' nennt. Knoten ohne Kinder heißen ''Blätter''.&lt;br /&gt;
&lt;br /&gt;
Ein ''Suchbaum'' hat zusätzlich die Eigenschaft, dass die Schlüssel jedes Knotens sortiert sind. Alle Schlüssel im linken Unterbaum sind kleiner, alle Schlüssel im rechten Unterbaum sind größer als ihr Vater. Wir wollen hierbei annehmen, dass jeder Schlüssel pro Datensatz nur einmal vorkommt, da sich sonst die &amp;gt;- oder &amp;lt;-Relation nicht mehr strikt erfüllen ließe.&lt;br /&gt;
&lt;br /&gt;
Um in einem Baum suchen zu können, wollen wir von zwei Annahmen ausgehen:&lt;br /&gt;
# Einfügen und Suchen im Baum wechseln sich ab. (Wenn das Suchen erst beginnt, nachdem alle Einfügungen erfolgt sind, wäre ein dynamisches Array mit binärer Suche wie oben wesentlich einfacher.)&lt;br /&gt;
# Der Schlüssel, der die Anordnung bestimmt, kennt eine [http://de.wikipedia.org/wiki/Ordnungsrelation Ordnung] (&amp;lt;-Relation oder &amp;gt;-Relation).&lt;br /&gt;
&lt;br /&gt;
Der folgende Python-Code zeigt beispielhaft, wie man in einem Suchbaum suchen könnte. Der Konstruktor für einen Knoten des Suchbaums ließe sich zum Beispiel so implementieren:&lt;br /&gt;
 &lt;br /&gt;
 class Node:&lt;br /&gt;
     def __init__(self, key):&lt;br /&gt;
         self.key = key&lt;br /&gt;
         self.left = self.right = None&lt;br /&gt;
 &lt;br /&gt;
 root = ...    # Wurzel des Suchbaums&lt;br /&gt;
 nodeFound = treeSearch(root, key)   # None, falls nichts gefunden&lt;br /&gt;
 &lt;br /&gt;
 def treeSearch(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return None&lt;br /&gt;
     elif node.key == key:&lt;br /&gt;
         return node&lt;br /&gt;
     elif key &amp;lt; node.key:&lt;br /&gt;
         return treeSearch(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         return treeSearch(node.right, key)&lt;br /&gt;
&lt;br /&gt;
Daraus resultiert der folgende Suchalgorithmus:&lt;br /&gt;
&lt;br /&gt;
 def treeSort(node,array):     # dynamisches Array als 2. Argument&lt;br /&gt;
     if node is None:     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
         return None:&lt;br /&gt;
     treeSort(node.left, array)     # rekursiv&lt;br /&gt;
     array.append(node.key)     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
     treeSort(node.right, array)     # rekursiv&lt;br /&gt;
&lt;br /&gt;
Komplexität:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 f(N)=\mathcal{O}(1)+f(N_\mathrm{left})+f(N_\mathrm{right})=\mathcal{O}(1)+\mathcal{O}(1)+f(N_\mathrm{leftleft})+f(N_\mathrm{leftright})+\mathcal{O}(1)+f(N_\mathrm{rightleft})&lt;br /&gt;
 +f(N_\mathrm{leftright})=N\ast\mathcal{O}(1)=\mathcal{O}(N)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sortier-Pseudocode:&lt;br /&gt;
&lt;br /&gt;
 Sortieren:&lt;br /&gt;
     (Array) a     # unsortiert&lt;br /&gt;
     (tree) t     # zunächst leer&lt;br /&gt;
 (dynamisches Array) r     # später sortiert&lt;br /&gt;
 for e in a:&lt;br /&gt;
     t = treeInsert(t, e)&lt;br /&gt;
 treeSort(t, r)&lt;br /&gt;
&lt;br /&gt;
== Insert ==&lt;br /&gt;
&lt;br /&gt;
Was passiert wenn der key (Schlüssel) schon vorhanden ist?&lt;br /&gt;
* error (exception)&lt;br /&gt;
* nichts einfügen&lt;br /&gt;
* nichts einfügen aber einen boolean return zurückgeben (einfügen=true, false)&lt;br /&gt;
* nochmals eingefügt (z.B. in der Node Klasse)&lt;br /&gt;
&lt;br /&gt;
Wobei die ersten 3 Punkte zur Mengensemantik gehören und der letzte eine Multimenge ist.&lt;br /&gt;
&lt;br /&gt;
Algorithmus im zweiten Fall (nichts einfügen):&lt;br /&gt;
&lt;br /&gt;
 def treeInsert(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return Node(key)     # Alternative Schreibweise: node = Node(key)&lt;br /&gt;
     if node.key == key:      # und dann:&lt;br /&gt;
         return node          # pass&lt;br /&gt;
     elif key &amp;lt; node.key:     # links im Baum&lt;br /&gt;
         node.left = treeInsert(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         node.right = treeInsert(node.right, key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Remove ==&lt;br /&gt;
Fälle:&lt;br /&gt;
# key (bzw. Knoten der key enthält) ist ein Blatt =&amp;gt; einfach löschen&lt;br /&gt;
# node hat &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; linken Unterbaum oder &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; rechten Unterbaum =&amp;gt; durch Unterbaum ersetzen&lt;br /&gt;
# node hat beide Unterbäume:&lt;br /&gt;
#* Suche Vorgänger: max k &amp;lt; key (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; keys); Vorgänger ist immer ein Fall 1 oder Fall 2&lt;br /&gt;
#*:=&amp;gt; ersetze node durch Vorgänger und entferne Vorgänger&lt;br /&gt;
&lt;br /&gt;
 def treePredecessor(node):     # wird nur bei Fall 3 aufgerufen&lt;br /&gt;
     node = node.left&lt;br /&gt;
     while node.right is not None:&lt;br /&gt;
         node = node.right&lt;br /&gt;
     return node&lt;br /&gt;
 &lt;br /&gt;
 def treeRemove(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return node&lt;br /&gt;
     if key &amp;lt; node.key:&lt;br /&gt;
         node.left = treeRemove(node.left, key)&lt;br /&gt;
     elif key &amp;gt; node.key:&lt;br /&gt;
         node.right = treeRemove(node.right, key)&lt;br /&gt;
     else:&lt;br /&gt;
         if node.left is None and node.right is None:     # Fall 1&lt;br /&gt;
             node = None&lt;br /&gt;
         elif node.left is None:     # Fall 2&lt;br /&gt;
             node = node.right       # +&lt;br /&gt;
         elif node.right is None:    # Fall 2&lt;br /&gt;
             node = node.left&lt;br /&gt;
         else:&lt;br /&gt;
             pred = treePredecessor(node)&lt;br /&gt;
             node.key = pred.key&lt;br /&gt;
             node.left = treeRemove(node.left, pred.key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Komplexitätsanalyse ==&lt;br /&gt;
&lt;br /&gt;
* Pfad (Zwischen node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;): &lt;br /&gt;
**Folge von Knoten (node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt;,...,node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt;), sodass:&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;ki&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;ki+1&amp;lt;/sub&amp;gt; haben eine gemeinsame Kante.&lt;br /&gt;
[[Image:Baum_Pfad.png]]&lt;br /&gt;
(Ein Baum ist ein Graph, indem es zwischen beliebigen Knoten stets genau einen Pfad gibt.)&lt;br /&gt;
&lt;br /&gt;
* Länge eines Pfades: Anzahl der Kanten = Anzahl der Knoten - 1&lt;br /&gt;
* Tiefe eines Knotens: Pfadlänge vom Knoten zur Wurzel des Baumes.&lt;br /&gt;
* Tiefe des Baumes: maximale Tiefe eines Knotens&lt;br /&gt;
* Balance eines Baumes: maximale Tiefe(k) - minimale Tiefe(k) (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; Blätter)&lt;br /&gt;
&lt;br /&gt;
== Ungünstigster Fall von treeSearch ==&lt;br /&gt;
&lt;br /&gt;
Komplexität von treeSearch = Länge des Pfades zum Knoten wo &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; gefunden wird, oder erkannt wird, dass &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; nicht im Baum ist.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; wird nicht gefunden, aber für diese Entscheidung muss der Längste Pfad vollständig durchlaufen werden.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(T)&amp;lt;/math&amp;gt; wo T = Tiefe des Baumes&lt;br /&gt;
*: [1,2,3,4,5]:&amp;lt;br /&amp;gt; [[Image:Balance.png]]&lt;br /&gt;
*: Tiefe T = 4, Balance = 4&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; wo N = Anzahl der Elemente.&lt;br /&gt;
&lt;br /&gt;
== Aufgabe ==&lt;br /&gt;
&lt;br /&gt;
Minimiere Balance (erzeuge balancierten Baum):&lt;br /&gt;
# Einfügen in geschickter Reihenfolge (siehe Übungsaufgabe)&lt;br /&gt;
# Selbstbalancierter Baum:&lt;br /&gt;
#* Überprüfen der Balance nach jedem Einfügen&lt;br /&gt;
#* Umstrukturieren des Baumes, falls Balance &amp;gt; 1 (Suchbaum-Bedingung muss erhalten bleiben)&lt;br /&gt;
#* AVL-Bäume (älteste Variante)&lt;br /&gt;
#* Rot-Schwarz-Bäume (verbreiteste Variante)&lt;br /&gt;
#* Treaps (flexibelste Variante, siehe Übung)&lt;br /&gt;
#* Splay trees&lt;br /&gt;
#* Andersson Trees (einfachste Variante)&lt;br /&gt;
(#* Skip Lists (schnellste Variante, aber kein Binärbaum))&lt;br /&gt;
&lt;br /&gt;
== Umstrukturieren, so dass Suchbaumbedingung erhalten bleibt: ==&lt;br /&gt;
&lt;br /&gt;
Rotation: elementare Umstrukturierungen&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum_Rotation.png]]&lt;br /&gt;
&lt;br /&gt;
 def rotateRight(node):&lt;br /&gt;
     newRoot = node.left&lt;br /&gt;
     node.left = newRoot.right&lt;br /&gt;
     newRoot.right = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
 def rotateLeft(node):&lt;br /&gt;
     newRoot = node.right&lt;br /&gt;
     node.right = newRoot.left&lt;br /&gt;
     newRoot.left = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
== Balance eines Suchbaumes ==&lt;br /&gt;
&lt;br /&gt;
===Balance eines Baumes zu  definieren:===&lt;br /&gt;
[[Image:Abbildung1.jpg|250px|right]]&lt;br /&gt;
*betrachte &amp;lt;tt&amp;gt;None&amp;lt;/tt&amp;gt; als zusätzlichen Knoten : '''sentinel'''   ([[englische Sprache|engl.]] für ''Wächter'')&lt;br /&gt;
*oder defeniere speziellen sentinel-Knoten&lt;br /&gt;
*RS - Pfades : von &amp;lt;tt&amp;gt;root&amp;lt;/tt&amp;gt; &amp;amp;rarr; ''sentinel''&lt;br /&gt;
*Länge eines Pfades ''P''      '''''|P|'''''&lt;br /&gt;
* &amp;lt;u&amp;gt;die Balance eines Baumes zu defenieren:&amp;lt;/u&amp;gt;&lt;br /&gt;
: '''''B = max P '''''&amp;lt;math&amp;gt; \in &amp;lt;/math&amp;gt; '''''{RS}    |P| - min |P|'''''&lt;br /&gt;
:'''''{RS}'''''  Menge aller RS-Pfade&lt;br /&gt;
;vollständiger Baum:  Balance &amp;lt;math&amp;gt;~=0&amp;lt;/math&amp;gt;&lt;br /&gt;
:Alle Knoten(außer Blättern) haben 2 Kinder.&lt;br /&gt;
;perfect balancierter Baum:  Balance  &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 1&lt;br /&gt;
'''alternative Defenition für perfect balancierte Bäume'''&lt;br /&gt;
:Für jeden Teilbaum gilt es: rechtes und linkes Kind (für jeden Knoten) sind auch wieder perfect balancierte Bäume und ihre Höhe unterscheidet sich höchstens um '''1'''.&lt;br /&gt;
&lt;br /&gt;
===Größe===&lt;br /&gt;
[[Image:baum-v.png|700px|right]]&lt;br /&gt;
====Größe des vollständigen Baumes====&lt;br /&gt;
Ebene K hat 2 &amp;lt;sup&amp;gt;k&amp;lt;/sup&amp;gt; Knoten.&lt;br /&gt;
Falls Tiefe = d , dann N = 2&amp;lt;sup&amp;gt;0&amp;lt;/sup&amp;gt; + 2&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;.....+ 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; = 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1&lt;br /&gt;
&lt;br /&gt;
====Größe des perfect balancierten Baumes====&lt;br /&gt;
Die Tiefe d, kann nicht besser sein als vollständiger Baum&lt;br /&gt;
:'''=&amp;gt;'''N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2 &amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
;schlechteste perfect balancierter Baum : ist ein vollständiger der Tiefe  d - 1 + 1, N &amp;lt;math&amp;gt; \ge&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:'''=&amp;gt;'''  2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1)   &amp;lt;math&amp;gt;~&amp;lt;&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt;)&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
d &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt;  log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; (d + 1)&lt;br /&gt;
&lt;br /&gt;
*Ergibt die Komplexität der Suche im schlechtesten Fall: Anzahl der Vergleiche pro Knoten( = 2 bzw. = 3)&amp;lt;math&amp;gt;\ast&amp;lt;/math&amp;gt;Anzahl der Knoten&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow ~f(N)\le 2d \le 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
*'''perfekt balancierter Baum '''=&amp;gt; AVL-Baum &lt;br /&gt;
*'''balancierter Baum''':&lt;br /&gt;
::&amp;lt;math&amp;gt;\forall  N:d(N)\le c \ast d (N)&amp;lt;/math&amp;gt; und  &amp;lt;math&amp;gt;1 \le c &amp;lt; \mathcal {1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:: d ist die Tiefe von perfekt balancierten Baum&lt;br /&gt;
&lt;br /&gt;
:''Komplexität der Suche'':&amp;lt;math&amp;gt; ~f(N)\le  c\ast 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
:algorithmisch einfacher als perfekt balancierter Baum, aber fast genauso schnell&lt;br /&gt;
&lt;br /&gt;
====Selbst-balancierter Baum====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  def insertTree(node,key):&lt;br /&gt;
    if node is sentinel:       #(None = sentinel)&lt;br /&gt;
        return Node(key)       &lt;br /&gt;
    if node.key == key:&lt;br /&gt;
        return Node&lt;br /&gt;
    if key &amp;lt; node.key:&lt;br /&gt;
        Node = insertTree(node.left, key)&lt;br /&gt;
    else:&lt;br /&gt;
        node.right = insertTree(node.right, key)&lt;br /&gt;
   #optimiere Balance:&lt;br /&gt;
        return node&lt;br /&gt;
&lt;br /&gt;
===Anderson-Bäume===&lt;br /&gt;
&lt;br /&gt;
  class Node:&lt;br /&gt;
    def__init__(self, key):&lt;br /&gt;
        self.key = key&lt;br /&gt;
        self.left = selft.right = None&lt;br /&gt;
        #einfügen:&lt;br /&gt;
        self.level = 1&lt;br /&gt;
&lt;br /&gt;
:''level'' : kodiert Abstand von sentinel&lt;br /&gt;
====Regeln====&lt;br /&gt;
Es gibt vertikale Kanten(parent.level == child.level + 1 ) und horizontale Kanten(parent.level == child.level)&lt;br /&gt;
:*verfeinerter Regel :  ''level kodiert reduzierten Abstand von Setinel''  (d.b. horizontale Kanten werden nicht gezählt)&lt;br /&gt;
::(die nächste zwei Regeln sichern die Balance):&lt;br /&gt;
::*alle RS-Pfade haben die gleiche reduzierte Länge oder ''root'' hat bei allen Pfaden das gleiche Level&lt;br /&gt;
::*kein Pfad hat 2 aufeinanderer folgende horizontale Kanten&lt;br /&gt;
:*nur Kanten zum rechten Kind dürfen horizontal sein&lt;br /&gt;
:*die reduzierte Höhe jedes Blatts ist '''hr=1'''&lt;br /&gt;
&lt;br /&gt;
====Beweis====&lt;br /&gt;
Vereinfachung des Algorithmus&lt;br /&gt;
:'''Satz''':ein Anderson-Baum ist balanciert.&lt;br /&gt;
*1. Sei '''hr'''- die reduzierte Höhe&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; jeder Teilbaum enthält mindestens &amp;lt;math&amp;gt;N\ge 2^{hr} -1 &amp;lt;/math&amp;gt;  Knoten&lt;br /&gt;
&lt;br /&gt;
::a). Blätter : reduzierte Höhe 1 =&amp;gt; &amp;lt;math&amp;gt; N\ge 2^{1} - 1 = 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
::b). inneren Knoten: jeder Unterbaum hat mindestens reduzierte Höhe &amp;lt;math&amp;gt; ~hr - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt;  jeder Unterbaum hat mindestens &amp;lt;math&amp;gt; ~2^{hr} -1 &amp;lt;/math&amp;gt; Knoten&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt;N \ge 2 (2^{hr-1} -1)+1 = 2^w - 2 + 1 = 2^{hr} -1&amp;lt;/math&amp;gt;&lt;br /&gt;
::::alle RS-Pfade heben gleiche Länge&lt;br /&gt;
[[Image:Anderson.png|700px|right]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
*2. Kein Pfad hat 2 aufeinanderfolgende horizontal Kanten&lt;br /&gt;
:::&amp;lt;math&amp;gt;\Rightarrow  d \le 2 hr + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
*3. zusammen:&lt;br /&gt;
:&amp;lt;math&amp;gt;N \ge 2^{\frac {d}{2}} - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;log_{2}{ N}+1\ge log 2^{\frac {d}{2}} - 1 \ge \frac {d}{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; ~d &amp;lt; 2 log_{2}{(N + 1)}&amp;lt;/math&amp;gt;     - balancierter Baum&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
:'''Suchzeit''' : &amp;lt;math&amp;gt; ~ f(N) = \mathcal{O}(log{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Wie erreicht man die Balance===&lt;br /&gt;
====Rotation====&lt;br /&gt;
[[Image:Abbildung4.jpg|text-top|350px|centre|Illustration von Rotation]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
   def rotateRight (node):&lt;br /&gt;
      root = node.left&lt;br /&gt;
      node.left = node.right&lt;br /&gt;
      root.right = node&lt;br /&gt;
      return root&lt;br /&gt;
   def rotateLeft(node):&lt;br /&gt;
      root = node.right&lt;br /&gt;
      node.right = node.left&lt;br /&gt;
      root.left = node&lt;br /&gt;
      return root&lt;br /&gt;
&lt;br /&gt;
====Optimierung der Balance====&lt;br /&gt;
   &lt;br /&gt;
&lt;br /&gt;
       if node.left is not sentinel and node.level==node.left.level:&lt;br /&gt;
            node &amp;gt; rotateRight(node)&lt;br /&gt;
       if node.right is not sentinel and node.right.right is  not sentinel and node.level==rotate.right.right.level:&lt;br /&gt;
            node = rotateLeft(node)&lt;br /&gt;
            node.level += 1&lt;/div&gt;</summary>
		<author><name>Nata</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1493</id>
		<title>Suchen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1493"/>
				<updated>2008-05-29T00:16:33Z</updated>
		
		<summary type="html">&lt;p&gt;Nata: /* Rotation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Es wäre super wenn jemand ganz kurz schreiben könnte, was am Donnerstag zu den Funktionen treeInsert/Remove/HasKey gemacht wurde, was man ja für den aktuellen Übungszettel braucht. Danke :-)&amp;lt;br /&amp;gt;&lt;br /&gt;
/edit: an eine Funktion &amp;quot;HasKey&amp;quot; kann ich mich aus der Vorlesung nicht erinnern. Wenn die jemand hat, bitte eintragen. - Protokollant&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--Hallo, ich kenne leider deinen Namen nicht, ich habe den Wiki-Eintrag von letztem Donnerstag gemacht.Da das Thema 'Dynamisches Array' noch Thema von letzter Woche war, hab ich die Wiederholung der amortisierten Kosten von diesem Mittwoch noch bei Effizienz eingetragen, du musst es also nicht noch mal in deinem Wiki eintragen. Du kannst dir aber gerne mal anschauen was ich geschrieben habe, vielleicht fallen dir noch ein paar Sachen ein, die man hätte besser machen können. lg, Franziska.--&amp;gt;&lt;br /&gt;
Das Suchen ist eine grundlegende Operation in der Informatik. Viele Probleme in der Informatik können auf Suchaufgaben zurückgeführt werden.&lt;br /&gt;
&lt;br /&gt;
Gemeint ist mit Suchen das Wiederauffinden einer oder mehrerer Datensätze aus einer Menge von früher gespeicherten Datensätzen. Ein paar einleitende Worte zum Suchproblem findet man [http://de.wikipedia.org/wiki/Suche hier].&lt;br /&gt;
&lt;br /&gt;
== Überblick verschiedener Suchmethoden ==&lt;br /&gt;
&lt;br /&gt;
Um sich der Vielseitigkeit von Suchproblemen bewusst zu werden, ist es sinnvoll, sich einen Überblick über verschiedene Suchmethoden zu verschaffen. &lt;br /&gt;
&lt;br /&gt;
Hier sei auch auf einen bereits existierenden Wikipedia-Artikel zu [http://de.wikipedia.org/wiki/Suchverfahren Suchverfahren] verwiesen.&lt;br /&gt;
&lt;br /&gt;
Allen gemeinsam ist die grundlegende Aufgabe, ein Datenelement mit bestimmten Eigenschaften aus einer großen Menge von Datenelementen zu selektieren.&lt;br /&gt;
Dies kann, natürlich ohne jeden Anspruch auf Vollständigkeit, nach einer der jetzt diskutierten Methoden geschehen:&lt;br /&gt;
&lt;br /&gt;
* '''Schlüsselsuche''': meint das Suchen von Elementen mit bestimmtem Schlüssel; ein klassisches Beispiel wäre das Suchen in einem Wörterbuch,  die Schlüssel entsprechen hier den Wörtern, die Datensätze wären die zu den Wörtern gehörigen Eintragungen.&lt;br /&gt;
&lt;br /&gt;
* '''Bereichssuche''': Im Allgemeinen meint die Bereichssuche in n-Dimensionen die Selektion von Elementen mit Eigenschaften aus einem bestimmten n-dimensionalen Volumen. Im eindimensionalen Fall will man alle Elemente finden, deren Eigenschaft(en) in einem bestimmten Intervall liegen. Die Verallgemeinerung auf n-Dimensionen ist offensichtlich. Ein Beispiel für die Bereichssuche in einer 3D-Kugel wäre ein Handy mit Geolokalisierung, welches alle Restaurants in einem Umkreis von 500m findet. Lineare Ungleichungen werden graphisch durch [http://de.wikipedia.org/wiki/Hyperebene Hyperebenen] repräsentiert. In 2D sind diese Hyperebenen Geraden. Die Ungleichungen können dann den Lösungsraum in irgendeiner Form begrenzen.&lt;br /&gt;
&lt;br /&gt;
* '''Ähnlichkeitssuche''': Finde Elemente, die gegebenen Eigenschaften möglichst ähnlich sind. Ein prominentes Beispiel ist Google (=Ähnlichkeit zwischen Suchbegriffen und Dokumenten) oder das Suchen des nächstengelegenen Restaurants (Ähnlichkeit zwischen eigener Position und Position des Restaurants). Ein wichtigster Spezialfall ist die ''nächste-nachbar Suche''.&lt;br /&gt;
&lt;br /&gt;
* '''Graphensuche''': Hier wäre beispielsweise das Problem optimaler Wege zu nennen (Navigationssuche). Dieser Punkt wird später im Verlauf der Vorlesung noch einmal aufgegriffen werden.&lt;br /&gt;
&lt;br /&gt;
Im jetzt Folgenden wird nur noch die ''Schlüsselsuche'' betrachtet werden.&lt;br /&gt;
&lt;br /&gt;
==Sequentielle Suche==&lt;br /&gt;
&lt;br /&gt;
Die ''sequentielle'' oder ''lineare'' Suche ist die 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 = sequentialSearch(a, key) &lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq &amp;lt;/math&amp;gt; foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def sequentialSearch(a, key):&lt;br /&gt;
    for i in range(len(a)):&lt;br /&gt;
        if a[i] == key:  # bzw. allgemeiner a[i].key == key &lt;br /&gt;
            return i&lt;br /&gt;
    return -1&lt;br /&gt;
&lt;br /&gt;
Wir wollen jetzt die Komplexität dieses Algorithmus bestimmen, wobei die Problemgröße durch &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt; gegeben ist. &lt;br /&gt;
&lt;br /&gt;
Dabei nimmt man an, dass der innerste Vergleich (a[i] == key) jeweils &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt; ist (diese Annahme könnte verletzt sein, wenn der Vergleichsoperator überladen ist und dadurch eine höhere Komplexität hat). Dieser Vergleich wird in der for-Schleife jeweils N-mal durchgeführt (&amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt;), so dass man nach der Verschachtelungsregel eine gesamte Komplexität von &amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt; erhält.&lt;br /&gt;
&lt;br /&gt;
Der Name ''lineare'' Suche rührt von diesem linearen Anwachsen der Komplexität mit der Arraygröße her.&lt;br /&gt;
&lt;br /&gt;
==Binäre Suche==&lt;br /&gt;
&lt;br /&gt;
Wie wir weiter unten zeigen werden, gestattet es diese Suchmethode, die Gesamtdauer der Suche in großen Datensätzen beträchtlich zu verringern. Die Methode beruht auf dem [http://de.wikipedia.org/wiki/Divide_and_Conquer Divide and Conquer-Prinzip], wobei die Suche in jedem Schritt rekursiv auf eine Hälfte des Datensatzes eingeschränkt wird. Weitere Details zur Methode sind [http://de.wikipedia.org/wiki/Bin%C3%A4re_Suche hier] zu finden. &lt;br /&gt;
&lt;br /&gt;
Die Methode ist nur dann anwendbar beziehungsweise effektiv, wenn folgendes gilt:&lt;br /&gt;
&lt;br /&gt;
# Auf der Eigenschaft der Daten, die zur Suche verwendet wird, ist eine Ordnung im Sinne von &amp;lt; oder &amp;gt; definiert.&lt;br /&gt;
# Wir wollen uns auf Datensätze beschränken, die schon fertig aufgebaut sind, in die also keine neuen Elemente mehr eingefügt werden, wenn man mit dem Suchen beginnt. Ist dies nicht der Fall, so ist unter Umständen die Implementierung über einen [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum Binärbaum] (siehe auch weiter unten) geschickter. &lt;br /&gt;
&lt;br /&gt;
Der folgende Algorithmus zeigt eine beispielhafte Implementierung der Methode (wir setzen wieder &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt;):&lt;br /&gt;
&lt;br /&gt;
 a = [...,...]     # array&lt;br /&gt;
 a.sort()   # sortiere über Ordnung des Schlüssels&lt;br /&gt;
 foundIndex = binSearch(a, key, 0, len(a))  # (Array, Schlüssel, von wo bis wo suchen im Array)&lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq&amp;lt;/math&amp;gt;  foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def binSearch(a, key, start, end):  # start ist 1. Index, end ist letzter Index + 1&lt;br /&gt;
    size = end - start   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if size &amp;lt;= 0:   # Bereich leer?  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return -1   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    center = (start + end)/2   # Integer Division, Ergebnis wird abgerundet, wichtig für ganzzahlige Indizes &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if a[center] == key:  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return center  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    elif a[center] &amp;lt; key:  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return binSearch(a, key, center + 1, end)  # Rekursion&lt;br /&gt;
    else:&lt;br /&gt;
        return binSearch(a, key, start + 1, center)  # Rekursion&lt;br /&gt;
&lt;br /&gt;
Zur Berechnung der Komplexität dieses Algorithmus vernachlässigen wir zunächst den Aufwand, den die Sortierung weiter oben verursacht. Dieser Schritt mag oder mag nicht zulässig sein.&lt;br /&gt;
&lt;br /&gt;
Nach der Sequenzregel haben auch alle &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;.  Es bleibt die Komplexität der Rekursion zu berechnen. Die gesamte Komplexität des Algorithmus (jetzt als Funktion f bezeichnet) setzt sich zusammen aus den oben erwähnten &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen sowie der Rekursion und ist &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f(N) = \mathcal{O}(1) + f(N/2) = \mathcal{O}(1) + \mathcal{O}(1) + f(N/4) = ... = \underbrace{\mathcal{O}(1) + ... + \mathcal{O}(1) + \underbrace{f(0)}_{\mathcal{O}(1)\, \rightarrow \,\mathrm{size-Abfrage}}}_{n+1 \,\mathrm{Terme}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Falls jetzt gilt &amp;lt;math&amp;gt; N = 2^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \rightarrow f(N) = \mathcal{O}(1) \cdot \mathcal{O}(n+1) = \mathcal{O}(n) = \mathcal{O}(\lg N) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Für große Datenmengen ist die ''binäre Suche'' also weit effizienter als die ''lineare Suche''. Verdoppelt sich beispielsweise die zu durchsuchende Datenmenge, so verdoppelt sich der Aufwand für die ''sequentielle Suche'' - bei der ''binären Suche'' hingegen benötigt man lediglich eine zusätzliche Vergleichsoperation. &lt;br /&gt;
&lt;br /&gt;
Für kleine Daten (&amp;lt;math&amp;gt; N = 4,\, 5 &amp;lt;/math&amp;gt;) ist die ''sequentielle Suche'' jedoch schneller als die ''binäre Suche'', da hier die rekursiven Funktionsaufrufe teurer als das Mehr an Vergleichen sind. Ein anderer ungünstiger Fall ist gegeben, wenn nur sehr wenige Suchanfragen erfolgen (weniger als &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; viele). Dann wird der Aufwand durch das Sortieren des Arrays dominiert, ist also &amp;lt;math&amp;gt;\mathcal{O}(N \lg N) &amp;lt;/math&amp;gt;. Auch dann ist sequentielle Suche vorzuziehen.&lt;br /&gt;
&lt;br /&gt;
Eine relativ einfache Möglichkeit, die ''binäre Suche'' zu verbessern, ist die sogenannte ''Interpolationssuche''. Hierbei wird die neue Position für die Suche, also die Mitte des Arrays, durch eine Schätzung ersetzt, die angibt, wo sich der Schlüssel innerhalb des Arrays befinden könnte. Bei der Suche in einem Telefonbuch nach dem Namen Zebra würde man ja auch nicht in der Mitte anfangen. Näheres hierzu im Buch von ''Sedgewick''.&lt;br /&gt;
&lt;br /&gt;
Um sich den Algorithmus der ''binären Suche'' klar zu machen, ist es instruktiv, sich die folgende Tabelle genauer anzusehen, die die sukzessive Belegung der Variablen bei verschiedenen Anfragen beschreibt. Die Testfälle wurden nach dem Prinzip des ''domain partitioning'' gewählt. Das zugehörige Array hat die Einträge&lt;br /&gt;
&lt;br /&gt;
 a = [2, 3, 4, 5, 6]&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align:center&amp;quot; border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;5&amp;quot; cellspacing=&amp;quot;0&amp;quot; &lt;br /&gt;
! gesuchter key   !!  start      !! end  !! size !! center !! return &amp;lt;br/&amp;gt; (-1 oder index)  !! Kommentare  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 4     ||0            || 5    ||  5   || 2   ||  2         || gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 2     || 0           || 5    ||  5   || 2   ||            || linker Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||           &lt;br /&gt;
|-&lt;br /&gt;
|       ||  0          || 1    ||  1   || 0   ||  0         || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 1     ||0            || 5    ||  5   || 2   ||            || links außerhalb&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 1    ||  1   || 0   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 0    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 6     ||0            || 5    ||  5   || 2   ||            || rechter Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    || 2    || 4   || 4          || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 5     ||0            || 5    ||  5   || 2   ||            || typischer Fall&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||3            || 5    ||  2   || 4   ||            ||  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       || 3           || 4    ||  1   || 3   || 3          || gefunden&lt;br /&gt;
|- &lt;br /&gt;
| 7     ||0            || 5    ||  5   || 2   ||            || rechts außerhalb        &lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    ||  2   || 4   ||            ||&lt;br /&gt;
|-&lt;br /&gt;
|       ||5            || 5    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Suche in einem Binärbaum ==&lt;br /&gt;
&lt;br /&gt;
Eine kurze Einführung in Binärbäume findet man [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum hier].&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum.png|text-top|300x300px|Zur Illustration von Bäumen]]&lt;br /&gt;
&lt;br /&gt;
Bäume sind zweidimensional verkettete Strukturen. Sie gehören zu den fundamentalen Datenstrukturen in der Informatik. Da man in Bäumen nicht nur Daten speichern kann, sondern auch relevante Beziehungen der Daten untereinander, festgelegt über eine Ordnung auf der vergleichenden Dateneigenschaft (''Schlüssel''), eignen sich Bäume also insbesondere, um gesuchte Daten schnell wieder auffinden zu können.&lt;br /&gt;
&lt;br /&gt;
Ein ''Binärbaum'' wie oben skizziert besteht aus einer Menge von ''Knoten'', die untereinander durch ''Kanten'' verbunden sind. Jeder Knoten hat einen linken und einen rechten Unterbaum, der auch leer sein kann (in Python ließe sich dies mit ''None'' implementieren). Führt eine Kante von Knoten A zu Knoten B, so heißt A Vater von B und B Kind von A. Es gibt genau einen Knoten ohne Vater, den man ''Wurzel'' nennt. Knoten ohne Kinder heißen ''Blätter''.&lt;br /&gt;
&lt;br /&gt;
Ein ''Suchbaum'' hat zusätzlich die Eigenschaft, dass die Schlüssel jedes Knotens sortiert sind. Alle Schlüssel im linken Unterbaum sind kleiner, alle Schlüssel im rechten Unterbaum sind größer als ihr Vater. Wir wollen hierbei annehmen, dass jeder Schlüssel pro Datensatz nur einmal vorkommt, da sich sonst die &amp;gt;- oder &amp;lt;-Relation nicht mehr strikt erfüllen ließe.&lt;br /&gt;
&lt;br /&gt;
Um in einem Baum suchen zu können, wollen wir von zwei Annahmen ausgehen:&lt;br /&gt;
# Einfügen und Suchen im Baum wechseln sich ab. (Wenn das Suchen erst beginnt, nachdem alle Einfügungen erfolgt sind, wäre ein dynamisches Array mit binärer Suche wie oben wesentlich einfacher.)&lt;br /&gt;
# Der Schlüssel, der die Anordnung bestimmt, kennt eine [http://de.wikipedia.org/wiki/Ordnungsrelation Ordnung] (&amp;lt;-Relation oder &amp;gt;-Relation).&lt;br /&gt;
&lt;br /&gt;
Der folgende Python-Code zeigt beispielhaft, wie man in einem Suchbaum suchen könnte. Der Konstruktor für einen Knoten des Suchbaums ließe sich zum Beispiel so implementieren:&lt;br /&gt;
 &lt;br /&gt;
 class Node:&lt;br /&gt;
     def __init__(self, key):&lt;br /&gt;
         self.key = key&lt;br /&gt;
         self.left = self.right = None&lt;br /&gt;
 &lt;br /&gt;
 root = ...    # Wurzel des Suchbaums&lt;br /&gt;
 nodeFound = treeSearch(root, key)   # None, falls nichts gefunden&lt;br /&gt;
 &lt;br /&gt;
 def treeSearch(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return None&lt;br /&gt;
     elif node.key == key:&lt;br /&gt;
         return node&lt;br /&gt;
     elif key &amp;lt; node.key:&lt;br /&gt;
         return treeSearch(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         return treeSearch(node.right, key)&lt;br /&gt;
&lt;br /&gt;
Daraus resultiert der folgende Suchalgorithmus:&lt;br /&gt;
&lt;br /&gt;
 def treeSort(node,array):     # dynamisches Array als 2. Argument&lt;br /&gt;
     if node is None:     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
         return None:&lt;br /&gt;
     treeSort(node.left, array)     # rekursiv&lt;br /&gt;
     array.append(node.key)     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
     treeSort(node.right, array)     # rekursiv&lt;br /&gt;
&lt;br /&gt;
Komplexität:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 f(N)=\mathcal{O}(1)+f(N_\mathrm{left})+f(N_\mathrm{right})=\mathcal{O}(1)+\mathcal{O}(1)+f(N_\mathrm{leftleft})+f(N_\mathrm{leftright})+\mathcal{O}(1)+f(N_\mathrm{rightleft})&lt;br /&gt;
 +f(N_\mathrm{leftright})=N\ast\mathcal{O}(1)=\mathcal{O}(N)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sortier-Pseudocode:&lt;br /&gt;
&lt;br /&gt;
 Sortieren:&lt;br /&gt;
     (Array) a     # unsortiert&lt;br /&gt;
     (tree) t     # zunächst leer&lt;br /&gt;
 (dynamisches Array) r     # später sortiert&lt;br /&gt;
 for e in a:&lt;br /&gt;
     t = treeInsert(t, e)&lt;br /&gt;
 treeSort(t, r)&lt;br /&gt;
&lt;br /&gt;
== Insert ==&lt;br /&gt;
&lt;br /&gt;
Was passiert wenn der key (Schlüssel) schon vorhanden ist?&lt;br /&gt;
* error (exception)&lt;br /&gt;
* nichts einfügen&lt;br /&gt;
* nichts einfügen aber einen boolean return zurückgeben (einfügen=true, false)&lt;br /&gt;
* nochmals eingefügt (z.B. in der Node Klasse)&lt;br /&gt;
&lt;br /&gt;
Wobei die ersten 3 Punkte zur Mengensemantik gehören und der letzte eine Multimenge ist.&lt;br /&gt;
&lt;br /&gt;
Algorithmus im zweiten Fall (nichts einfügen):&lt;br /&gt;
&lt;br /&gt;
 def treeInsert(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return Node(key)     # Alternative Schreibweise: node = Node(key)&lt;br /&gt;
     if node.key == key:      # und dann:&lt;br /&gt;
         return node          # pass&lt;br /&gt;
     elif key &amp;lt; node.key:     # links im Baum&lt;br /&gt;
         node.left = treeInsert(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         node.right = treeInsert(node.right, key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Remove ==&lt;br /&gt;
Fälle:&lt;br /&gt;
# key (bzw. Knoten der key enthält) ist ein Blatt =&amp;gt; einfach löschen&lt;br /&gt;
# node hat &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; linken Unterbaum oder &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; rechten Unterbaum =&amp;gt; durch Unterbaum ersetzen&lt;br /&gt;
# node hat beide Unterbäume:&lt;br /&gt;
#* Suche Vorgänger: max k &amp;lt; key (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; keys); Vorgänger ist immer ein Fall 1 oder Fall 2&lt;br /&gt;
#*:=&amp;gt; ersetze node durch Vorgänger und entferne Vorgänger&lt;br /&gt;
&lt;br /&gt;
 def treePredecessor(node):     # wird nur bei Fall 3 aufgerufen&lt;br /&gt;
     node = node.left&lt;br /&gt;
     while node.right is not None:&lt;br /&gt;
         node = node.right&lt;br /&gt;
     return node&lt;br /&gt;
 &lt;br /&gt;
 def treeRemove(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return node&lt;br /&gt;
     if key &amp;lt; node.key:&lt;br /&gt;
         node.left = treeRemove(node.left, key)&lt;br /&gt;
     elif key &amp;gt; node.key:&lt;br /&gt;
         node.right = treeRemove(node.right, key)&lt;br /&gt;
     else:&lt;br /&gt;
         if node.left is None and node.right is None:     # Fall 1&lt;br /&gt;
             node = None&lt;br /&gt;
         elif node.left is None:     # Fall 2&lt;br /&gt;
             node = node.right       # +&lt;br /&gt;
         elif node.right is None:    # Fall 2&lt;br /&gt;
             node = node.left&lt;br /&gt;
         else:&lt;br /&gt;
             pred = treePredecessor(node)&lt;br /&gt;
             node.key = pred.key&lt;br /&gt;
             node.left = treeRemove(node.left, pred.key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Komplexitätsanalyse ==&lt;br /&gt;
&lt;br /&gt;
* Pfad (Zwischen node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;): &lt;br /&gt;
**Folge von Knoten (node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt;,...,node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt;), sodass:&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;ki&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;ki+1&amp;lt;/sub&amp;gt; haben eine gemeinsame Kante.&lt;br /&gt;
[[Image:Baum_Pfad.png]]&lt;br /&gt;
(Ein Baum ist ein Graph, indem es zwischen beliebigen Knoten stets genau einen Pfad gibt.)&lt;br /&gt;
&lt;br /&gt;
* Länge eines Pfades: Anzahl der Kanten = Anzahl der Knoten - 1&lt;br /&gt;
* Tiefe eines Knotens: Pfadlänge vom Knoten zur Wurzel des Baumes.&lt;br /&gt;
* Tiefe des Baumes: maximale Tiefe eines Knotens&lt;br /&gt;
* Balance eines Baumes: maximale Tiefe(k) - minimale Tiefe(k) (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; Blätter)&lt;br /&gt;
&lt;br /&gt;
== Ungünstigster Fall von treeSearch ==&lt;br /&gt;
&lt;br /&gt;
Komplexität von treeSearch = Länge des Pfades zum Knoten wo &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; gefunden wird, oder erkannt wird, dass &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; nicht im Baum ist.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; wird nicht gefunden, aber für diese Entscheidung muss der Längste Pfad vollständig durchlaufen werden.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(T)&amp;lt;/math&amp;gt; wo T = Tiefe des Baumes&lt;br /&gt;
*: [1,2,3,4,5]:&amp;lt;br /&amp;gt; [[Image:Balance.png]]&lt;br /&gt;
*: Tiefe T = 4, Balance = 4&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; wo N = Anzahl der Elemente.&lt;br /&gt;
&lt;br /&gt;
== Aufgabe ==&lt;br /&gt;
&lt;br /&gt;
Minimiere Balance (erzeuge balancierten Baum):&lt;br /&gt;
# Einfügen in geschickter Reihenfolge (siehe Übungsaufgabe)&lt;br /&gt;
# Selbstbalancierter Baum:&lt;br /&gt;
#* Überprüfen der Balance nach jedem Einfügen&lt;br /&gt;
#* Umstrukturieren des Baumes, falls Balance &amp;gt; 1 (Suchbaum-Bedingung muss erhalten bleiben)&lt;br /&gt;
#* AVL-Bäume (älteste Variante)&lt;br /&gt;
#* Rot-Schwarz-Bäume (verbreiteste Variante)&lt;br /&gt;
#* Treaps (flexibelste Variante, siehe Übung)&lt;br /&gt;
#* Splay trees&lt;br /&gt;
#* Andersson Trees (einfachste Variante)&lt;br /&gt;
(#* Skip Lists (schnellste Variante, aber kein Binärbaum))&lt;br /&gt;
&lt;br /&gt;
== Umstrukturieren, so dass Suchbaumbedingung erhalten bleibt: ==&lt;br /&gt;
&lt;br /&gt;
Rotation: elementare Umstrukturierungen&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum_Rotation.png]]&lt;br /&gt;
&lt;br /&gt;
 def rotateRight(node):&lt;br /&gt;
     newRoot = node.left&lt;br /&gt;
     node.left = newRoot.right&lt;br /&gt;
     newRoot.right = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
 def rotateLeft(node):&lt;br /&gt;
     newRoot = node.right&lt;br /&gt;
     node.right = newRoot.left&lt;br /&gt;
     newRoot.left = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
== Balance eines Suchbaumes ==&lt;br /&gt;
&lt;br /&gt;
===Balance eines Baumes zu  definieren:===&lt;br /&gt;
[[Image:Abbildung1.jpg|250px|right]]&lt;br /&gt;
*betrachte &amp;lt;tt&amp;gt;None&amp;lt;/tt&amp;gt; als zusätzlichen Knoten : '''sentinel'''   ([[englische Sprache|engl.]] für ''Wächter'')&lt;br /&gt;
*oder defeniere speziellen sentinel-Knoten&lt;br /&gt;
*RS - Pfades : von &amp;lt;tt&amp;gt;root&amp;lt;/tt&amp;gt; &amp;amp;rarr; ''sentinel''&lt;br /&gt;
*Länge eines Pfades ''P''      '''''|P|'''''&lt;br /&gt;
* &amp;lt;u&amp;gt;die Balance eines Baumes zu defenieren:&amp;lt;/u&amp;gt;&lt;br /&gt;
: '''''B = max P '''''&amp;lt;math&amp;gt; \in &amp;lt;/math&amp;gt; '''''{RS}    |P| - min |P|'''''&lt;br /&gt;
:'''''{RS}'''''  Menge aller RS-Pfade&lt;br /&gt;
;vollständiger Baum:  Balance &amp;lt;math&amp;gt;~=0&amp;lt;/math&amp;gt;&lt;br /&gt;
:Alle Knoten(außer Blättern) haben 2 Kinder.&lt;br /&gt;
;perfect balancierter Baum:  Balance  &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 1&lt;br /&gt;
'''alternative Defenition für perfect balancierte Bäume'''&lt;br /&gt;
:Für jeden Teilbaum gilt es: rechtes und linkes Kind (für jeden Knoten) sind auch wieder perfect balancierte Bäume und ihre Höhe unterscheidet sich höchstens um '''1'''.&lt;br /&gt;
&lt;br /&gt;
===Größe===&lt;br /&gt;
[[Image:baum-v.png|700px|right]]&lt;br /&gt;
====Größe des vollständigen Baumes====&lt;br /&gt;
Ebene K hat 2 &amp;lt;sup&amp;gt;k&amp;lt;/sup&amp;gt; Knoten.&lt;br /&gt;
Falls Tiefe = d , dann N = 2&amp;lt;sup&amp;gt;0&amp;lt;/sup&amp;gt; + 2&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;.....+ 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; = 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1&lt;br /&gt;
&lt;br /&gt;
====Größe des perfect balancierten Baumes====&lt;br /&gt;
Die Tiefe d, kann nicht besser sein als vollständiger Baum&lt;br /&gt;
:'''=&amp;gt;'''N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2 &amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
;schlechteste perfect balancierter Baum : ist ein vollständiger der Tiefe  d - 1 + 1, N &amp;lt;math&amp;gt; \ge&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:'''=&amp;gt;'''  2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1)   &amp;lt;math&amp;gt;~&amp;lt;&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt;)&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
d &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt;  log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; (d + 1)&lt;br /&gt;
&lt;br /&gt;
*Ergibt die Komplexität der Suche im schlechtesten Fall: Anzahl der Vergleiche pro Knoten( = 2 bzw. = 3)&amp;lt;math&amp;gt;\ast&amp;lt;/math&amp;gt;Anzahl der Knoten&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow ~f(N)\le 2d \le 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
*'''perfekt balancierter Baum '''=&amp;gt; AVL-Baum &lt;br /&gt;
*'''balancierter Baum''':&lt;br /&gt;
::&amp;lt;math&amp;gt;\forall  N:d(N)\le c \ast d (N)&amp;lt;/math&amp;gt; und  &amp;lt;math&amp;gt;1 \le c &amp;lt; \mathcal {1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:: d ist die Tiefe von perfekt balancierten Baum&lt;br /&gt;
&lt;br /&gt;
:''Komplexität der Suche'':&amp;lt;math&amp;gt; ~f(N)\le  c\ast 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
:algorithmisch einfacher als perfekt balancierter Baum, aber fast genauso schnell&lt;br /&gt;
&lt;br /&gt;
====Selbst-balancierter Baum====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  def insertTree(node,key):&lt;br /&gt;
    if node is sentinel:       #(None = sentinel)&lt;br /&gt;
        return Node(key)       &lt;br /&gt;
    if node.key == key:&lt;br /&gt;
        return Node&lt;br /&gt;
    if key &amp;lt; node.key:&lt;br /&gt;
        Node = insertTree(node.left, key)&lt;br /&gt;
    else:&lt;br /&gt;
        node.right = insertTree(node.right, key)&lt;br /&gt;
   #optimiere Balance:&lt;br /&gt;
        return node&lt;br /&gt;
&lt;br /&gt;
===Anderson-Bäume===&lt;br /&gt;
&lt;br /&gt;
  class Node:&lt;br /&gt;
    def__init__(self, key):&lt;br /&gt;
        self.key = key&lt;br /&gt;
        self.left = selft.right = None&lt;br /&gt;
        #einfügen:&lt;br /&gt;
        self.level = 1&lt;br /&gt;
&lt;br /&gt;
:''level'' : kodiert Abstand von sentinel&lt;br /&gt;
====Regeln====&lt;br /&gt;
Es gibt vertikale Kanten(parent.level == child.level + 1 ) und horizontale Kanten(parent.level == child.level)&lt;br /&gt;
:*verfeinerter Regel :  ''level kodiert reduzierten Abstand von Setinel''  (d.b. horizontale Kanten werden nicht gezählt)&lt;br /&gt;
::(die nächste zwei Regeln sichern die Balance):&lt;br /&gt;
::*alle RS-Pfade haben die gleiche reduzierte Länge oder ''root'' hat bei allen Pfaden das gleiche Level&lt;br /&gt;
::*kein Pfad hat 2 aufeinanderer folgende horizontale Kanten&lt;br /&gt;
:*nur Kanten zum rechten Kind dürfen horizontal sein&lt;br /&gt;
:*die reduzierte Höhe jedes Blatts ist '''hr=1'''&lt;br /&gt;
&lt;br /&gt;
====Beweis====&lt;br /&gt;
Vereinfachung des Algorithmus&lt;br /&gt;
:'''Satz''':ein Anderson-Baum ist balanciert.&lt;br /&gt;
*1. Sei '''hr'''- die reduzierte Höhe&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; jeder Teilbaum enthält mindestens &amp;lt;math&amp;gt;N\ge 2^{hr} -1 &amp;lt;/math&amp;gt;  Knoten&lt;br /&gt;
&lt;br /&gt;
::a). Blätter : reduzierte Höhe 1 =&amp;gt; &amp;lt;math&amp;gt; N\ge 2^{1} - 1 = 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
::b). inneren Knoten: jeder Unterbaum hat mindestens reduzierte Höhe &amp;lt;math&amp;gt; ~hr - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt;  jeder Unterbaum hat mindestens &amp;lt;math&amp;gt; ~2^{hr} -1 &amp;lt;/math&amp;gt; Knoten&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt;N \ge 2 (2^{hr-1} -1)+1 = 2^w - 2 + 1 = 2^{hr} -1&amp;lt;/math&amp;gt;&lt;br /&gt;
::::alle RS-Pfade heben gleiche Länge&lt;br /&gt;
[[Image:Anderson.png|700px|right]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
*2. Kein Pfad hat 2 aufeinanderfolgende horizontal Kanten&lt;br /&gt;
:::&amp;lt;math&amp;gt;\Rightarrow  d \le 2 hr + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
*3. zusammen:&lt;br /&gt;
:&amp;lt;math&amp;gt;N \ge 2^{\frac {d}{2}} - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;log_{2}{ N}+1\ge log 2^{\frac {d}{2}} - 1 \ge \frac {d}{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; ~d &amp;lt; 2 log_{2}{(N + 1)}&amp;lt;/math&amp;gt;     - balancierter Baum&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
:'''Suchzeit''' : &amp;lt;math&amp;gt; ~ f(N) = \mathcal{O}(log{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Wie erreicht man die Balance===&lt;br /&gt;
====Rotation====&lt;br /&gt;
[[Image:Abbildung4.jpg|350px|centre|Illustration von Rotation]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
   def rotateRight (node):&lt;br /&gt;
      root = node.left&lt;br /&gt;
      node.left = node.right&lt;br /&gt;
      root.right = node&lt;br /&gt;
      return root&lt;br /&gt;
   def rotateLeft(node):&lt;br /&gt;
      root = node.right&lt;br /&gt;
      node.right = node.left&lt;br /&gt;
      root.left = node&lt;br /&gt;
      return root&lt;br /&gt;
&lt;br /&gt;
====Optimierung der Balance====&lt;br /&gt;
   &lt;br /&gt;
&lt;br /&gt;
       if node.left is not sentinel and node.level==node.left.level:&lt;br /&gt;
            node &amp;gt; rotateRight(node)&lt;br /&gt;
       if node.right is not sentinel and node.right.right is  not sentinel and node.level==rotate.right.right.level:&lt;br /&gt;
            node = rotateLeft(node)&lt;br /&gt;
            node.level += 1&lt;/div&gt;</summary>
		<author><name>Nata</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1492</id>
		<title>Suchen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1492"/>
				<updated>2008-05-29T00:16:02Z</updated>
		
		<summary type="html">&lt;p&gt;Nata: /* Rotation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Es wäre super wenn jemand ganz kurz schreiben könnte, was am Donnerstag zu den Funktionen treeInsert/Remove/HasKey gemacht wurde, was man ja für den aktuellen Übungszettel braucht. Danke :-)&amp;lt;br /&amp;gt;&lt;br /&gt;
/edit: an eine Funktion &amp;quot;HasKey&amp;quot; kann ich mich aus der Vorlesung nicht erinnern. Wenn die jemand hat, bitte eintragen. - Protokollant&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--Hallo, ich kenne leider deinen Namen nicht, ich habe den Wiki-Eintrag von letztem Donnerstag gemacht.Da das Thema 'Dynamisches Array' noch Thema von letzter Woche war, hab ich die Wiederholung der amortisierten Kosten von diesem Mittwoch noch bei Effizienz eingetragen, du musst es also nicht noch mal in deinem Wiki eintragen. Du kannst dir aber gerne mal anschauen was ich geschrieben habe, vielleicht fallen dir noch ein paar Sachen ein, die man hätte besser machen können. lg, Franziska.--&amp;gt;&lt;br /&gt;
Das Suchen ist eine grundlegende Operation in der Informatik. Viele Probleme in der Informatik können auf Suchaufgaben zurückgeführt werden.&lt;br /&gt;
&lt;br /&gt;
Gemeint ist mit Suchen das Wiederauffinden einer oder mehrerer Datensätze aus einer Menge von früher gespeicherten Datensätzen. Ein paar einleitende Worte zum Suchproblem findet man [http://de.wikipedia.org/wiki/Suche hier].&lt;br /&gt;
&lt;br /&gt;
== Überblick verschiedener Suchmethoden ==&lt;br /&gt;
&lt;br /&gt;
Um sich der Vielseitigkeit von Suchproblemen bewusst zu werden, ist es sinnvoll, sich einen Überblick über verschiedene Suchmethoden zu verschaffen. &lt;br /&gt;
&lt;br /&gt;
Hier sei auch auf einen bereits existierenden Wikipedia-Artikel zu [http://de.wikipedia.org/wiki/Suchverfahren Suchverfahren] verwiesen.&lt;br /&gt;
&lt;br /&gt;
Allen gemeinsam ist die grundlegende Aufgabe, ein Datenelement mit bestimmten Eigenschaften aus einer großen Menge von Datenelementen zu selektieren.&lt;br /&gt;
Dies kann, natürlich ohne jeden Anspruch auf Vollständigkeit, nach einer der jetzt diskutierten Methoden geschehen:&lt;br /&gt;
&lt;br /&gt;
* '''Schlüsselsuche''': meint das Suchen von Elementen mit bestimmtem Schlüssel; ein klassisches Beispiel wäre das Suchen in einem Wörterbuch,  die Schlüssel entsprechen hier den Wörtern, die Datensätze wären die zu den Wörtern gehörigen Eintragungen.&lt;br /&gt;
&lt;br /&gt;
* '''Bereichssuche''': Im Allgemeinen meint die Bereichssuche in n-Dimensionen die Selektion von Elementen mit Eigenschaften aus einem bestimmten n-dimensionalen Volumen. Im eindimensionalen Fall will man alle Elemente finden, deren Eigenschaft(en) in einem bestimmten Intervall liegen. Die Verallgemeinerung auf n-Dimensionen ist offensichtlich. Ein Beispiel für die Bereichssuche in einer 3D-Kugel wäre ein Handy mit Geolokalisierung, welches alle Restaurants in einem Umkreis von 500m findet. Lineare Ungleichungen werden graphisch durch [http://de.wikipedia.org/wiki/Hyperebene Hyperebenen] repräsentiert. In 2D sind diese Hyperebenen Geraden. Die Ungleichungen können dann den Lösungsraum in irgendeiner Form begrenzen.&lt;br /&gt;
&lt;br /&gt;
* '''Ähnlichkeitssuche''': Finde Elemente, die gegebenen Eigenschaften möglichst ähnlich sind. Ein prominentes Beispiel ist Google (=Ähnlichkeit zwischen Suchbegriffen und Dokumenten) oder das Suchen des nächstengelegenen Restaurants (Ähnlichkeit zwischen eigener Position und Position des Restaurants). Ein wichtigster Spezialfall ist die ''nächste-nachbar Suche''.&lt;br /&gt;
&lt;br /&gt;
* '''Graphensuche''': Hier wäre beispielsweise das Problem optimaler Wege zu nennen (Navigationssuche). Dieser Punkt wird später im Verlauf der Vorlesung noch einmal aufgegriffen werden.&lt;br /&gt;
&lt;br /&gt;
Im jetzt Folgenden wird nur noch die ''Schlüsselsuche'' betrachtet werden.&lt;br /&gt;
&lt;br /&gt;
==Sequentielle Suche==&lt;br /&gt;
&lt;br /&gt;
Die ''sequentielle'' oder ''lineare'' Suche ist die 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 = sequentialSearch(a, key) &lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq &amp;lt;/math&amp;gt; foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def sequentialSearch(a, key):&lt;br /&gt;
    for i in range(len(a)):&lt;br /&gt;
        if a[i] == key:  # bzw. allgemeiner a[i].key == key &lt;br /&gt;
            return i&lt;br /&gt;
    return -1&lt;br /&gt;
&lt;br /&gt;
Wir wollen jetzt die Komplexität dieses Algorithmus bestimmen, wobei die Problemgröße durch &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt; gegeben ist. &lt;br /&gt;
&lt;br /&gt;
Dabei nimmt man an, dass der innerste Vergleich (a[i] == key) jeweils &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt; ist (diese Annahme könnte verletzt sein, wenn der Vergleichsoperator überladen ist und dadurch eine höhere Komplexität hat). Dieser Vergleich wird in der for-Schleife jeweils N-mal durchgeführt (&amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt;), so dass man nach der Verschachtelungsregel eine gesamte Komplexität von &amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt; erhält.&lt;br /&gt;
&lt;br /&gt;
Der Name ''lineare'' Suche rührt von diesem linearen Anwachsen der Komplexität mit der Arraygröße her.&lt;br /&gt;
&lt;br /&gt;
==Binäre Suche==&lt;br /&gt;
&lt;br /&gt;
Wie wir weiter unten zeigen werden, gestattet es diese Suchmethode, die Gesamtdauer der Suche in großen Datensätzen beträchtlich zu verringern. Die Methode beruht auf dem [http://de.wikipedia.org/wiki/Divide_and_Conquer Divide and Conquer-Prinzip], wobei die Suche in jedem Schritt rekursiv auf eine Hälfte des Datensatzes eingeschränkt wird. Weitere Details zur Methode sind [http://de.wikipedia.org/wiki/Bin%C3%A4re_Suche hier] zu finden. &lt;br /&gt;
&lt;br /&gt;
Die Methode ist nur dann anwendbar beziehungsweise effektiv, wenn folgendes gilt:&lt;br /&gt;
&lt;br /&gt;
# Auf der Eigenschaft der Daten, die zur Suche verwendet wird, ist eine Ordnung im Sinne von &amp;lt; oder &amp;gt; definiert.&lt;br /&gt;
# Wir wollen uns auf Datensätze beschränken, die schon fertig aufgebaut sind, in die also keine neuen Elemente mehr eingefügt werden, wenn man mit dem Suchen beginnt. Ist dies nicht der Fall, so ist unter Umständen die Implementierung über einen [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum Binärbaum] (siehe auch weiter unten) geschickter. &lt;br /&gt;
&lt;br /&gt;
Der folgende Algorithmus zeigt eine beispielhafte Implementierung der Methode (wir setzen wieder &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt;):&lt;br /&gt;
&lt;br /&gt;
 a = [...,...]     # array&lt;br /&gt;
 a.sort()   # sortiere über Ordnung des Schlüssels&lt;br /&gt;
 foundIndex = binSearch(a, key, 0, len(a))  # (Array, Schlüssel, von wo bis wo suchen im Array)&lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq&amp;lt;/math&amp;gt;  foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def binSearch(a, key, start, end):  # start ist 1. Index, end ist letzter Index + 1&lt;br /&gt;
    size = end - start   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if size &amp;lt;= 0:   # Bereich leer?  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return -1   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    center = (start + end)/2   # Integer Division, Ergebnis wird abgerundet, wichtig für ganzzahlige Indizes &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if a[center] == key:  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return center  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    elif a[center] &amp;lt; key:  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return binSearch(a, key, center + 1, end)  # Rekursion&lt;br /&gt;
    else:&lt;br /&gt;
        return binSearch(a, key, start + 1, center)  # Rekursion&lt;br /&gt;
&lt;br /&gt;
Zur Berechnung der Komplexität dieses Algorithmus vernachlässigen wir zunächst den Aufwand, den die Sortierung weiter oben verursacht. Dieser Schritt mag oder mag nicht zulässig sein.&lt;br /&gt;
&lt;br /&gt;
Nach der Sequenzregel haben auch alle &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;.  Es bleibt die Komplexität der Rekursion zu berechnen. Die gesamte Komplexität des Algorithmus (jetzt als Funktion f bezeichnet) setzt sich zusammen aus den oben erwähnten &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen sowie der Rekursion und ist &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f(N) = \mathcal{O}(1) + f(N/2) = \mathcal{O}(1) + \mathcal{O}(1) + f(N/4) = ... = \underbrace{\mathcal{O}(1) + ... + \mathcal{O}(1) + \underbrace{f(0)}_{\mathcal{O}(1)\, \rightarrow \,\mathrm{size-Abfrage}}}_{n+1 \,\mathrm{Terme}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Falls jetzt gilt &amp;lt;math&amp;gt; N = 2^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \rightarrow f(N) = \mathcal{O}(1) \cdot \mathcal{O}(n+1) = \mathcal{O}(n) = \mathcal{O}(\lg N) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Für große Datenmengen ist die ''binäre Suche'' also weit effizienter als die ''lineare Suche''. Verdoppelt sich beispielsweise die zu durchsuchende Datenmenge, so verdoppelt sich der Aufwand für die ''sequentielle Suche'' - bei der ''binären Suche'' hingegen benötigt man lediglich eine zusätzliche Vergleichsoperation. &lt;br /&gt;
&lt;br /&gt;
Für kleine Daten (&amp;lt;math&amp;gt; N = 4,\, 5 &amp;lt;/math&amp;gt;) ist die ''sequentielle Suche'' jedoch schneller als die ''binäre Suche'', da hier die rekursiven Funktionsaufrufe teurer als das Mehr an Vergleichen sind. Ein anderer ungünstiger Fall ist gegeben, wenn nur sehr wenige Suchanfragen erfolgen (weniger als &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; viele). Dann wird der Aufwand durch das Sortieren des Arrays dominiert, ist also &amp;lt;math&amp;gt;\mathcal{O}(N \lg N) &amp;lt;/math&amp;gt;. Auch dann ist sequentielle Suche vorzuziehen.&lt;br /&gt;
&lt;br /&gt;
Eine relativ einfache Möglichkeit, die ''binäre Suche'' zu verbessern, ist die sogenannte ''Interpolationssuche''. Hierbei wird die neue Position für die Suche, also die Mitte des Arrays, durch eine Schätzung ersetzt, die angibt, wo sich der Schlüssel innerhalb des Arrays befinden könnte. Bei der Suche in einem Telefonbuch nach dem Namen Zebra würde man ja auch nicht in der Mitte anfangen. Näheres hierzu im Buch von ''Sedgewick''.&lt;br /&gt;
&lt;br /&gt;
Um sich den Algorithmus der ''binären Suche'' klar zu machen, ist es instruktiv, sich die folgende Tabelle genauer anzusehen, die die sukzessive Belegung der Variablen bei verschiedenen Anfragen beschreibt. Die Testfälle wurden nach dem Prinzip des ''domain partitioning'' gewählt. Das zugehörige Array hat die Einträge&lt;br /&gt;
&lt;br /&gt;
 a = [2, 3, 4, 5, 6]&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align:center&amp;quot; border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;5&amp;quot; cellspacing=&amp;quot;0&amp;quot; &lt;br /&gt;
! gesuchter key   !!  start      !! end  !! size !! center !! return &amp;lt;br/&amp;gt; (-1 oder index)  !! Kommentare  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 4     ||0            || 5    ||  5   || 2   ||  2         || gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 2     || 0           || 5    ||  5   || 2   ||            || linker Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||           &lt;br /&gt;
|-&lt;br /&gt;
|       ||  0          || 1    ||  1   || 0   ||  0         || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 1     ||0            || 5    ||  5   || 2   ||            || links außerhalb&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 1    ||  1   || 0   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 0    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 6     ||0            || 5    ||  5   || 2   ||            || rechter Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    || 2    || 4   || 4          || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 5     ||0            || 5    ||  5   || 2   ||            || typischer Fall&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||3            || 5    ||  2   || 4   ||            ||  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       || 3           || 4    ||  1   || 3   || 3          || gefunden&lt;br /&gt;
|- &lt;br /&gt;
| 7     ||0            || 5    ||  5   || 2   ||            || rechts außerhalb        &lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    ||  2   || 4   ||            ||&lt;br /&gt;
|-&lt;br /&gt;
|       ||5            || 5    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Suche in einem Binärbaum ==&lt;br /&gt;
&lt;br /&gt;
Eine kurze Einführung in Binärbäume findet man [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum hier].&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum.png|text-top|300x300px|Zur Illustration von Bäumen]]&lt;br /&gt;
&lt;br /&gt;
Bäume sind zweidimensional verkettete Strukturen. Sie gehören zu den fundamentalen Datenstrukturen in der Informatik. Da man in Bäumen nicht nur Daten speichern kann, sondern auch relevante Beziehungen der Daten untereinander, festgelegt über eine Ordnung auf der vergleichenden Dateneigenschaft (''Schlüssel''), eignen sich Bäume also insbesondere, um gesuchte Daten schnell wieder auffinden zu können.&lt;br /&gt;
&lt;br /&gt;
Ein ''Binärbaum'' wie oben skizziert besteht aus einer Menge von ''Knoten'', die untereinander durch ''Kanten'' verbunden sind. Jeder Knoten hat einen linken und einen rechten Unterbaum, der auch leer sein kann (in Python ließe sich dies mit ''None'' implementieren). Führt eine Kante von Knoten A zu Knoten B, so heißt A Vater von B und B Kind von A. Es gibt genau einen Knoten ohne Vater, den man ''Wurzel'' nennt. Knoten ohne Kinder heißen ''Blätter''.&lt;br /&gt;
&lt;br /&gt;
Ein ''Suchbaum'' hat zusätzlich die Eigenschaft, dass die Schlüssel jedes Knotens sortiert sind. Alle Schlüssel im linken Unterbaum sind kleiner, alle Schlüssel im rechten Unterbaum sind größer als ihr Vater. Wir wollen hierbei annehmen, dass jeder Schlüssel pro Datensatz nur einmal vorkommt, da sich sonst die &amp;gt;- oder &amp;lt;-Relation nicht mehr strikt erfüllen ließe.&lt;br /&gt;
&lt;br /&gt;
Um in einem Baum suchen zu können, wollen wir von zwei Annahmen ausgehen:&lt;br /&gt;
# Einfügen und Suchen im Baum wechseln sich ab. (Wenn das Suchen erst beginnt, nachdem alle Einfügungen erfolgt sind, wäre ein dynamisches Array mit binärer Suche wie oben wesentlich einfacher.)&lt;br /&gt;
# Der Schlüssel, der die Anordnung bestimmt, kennt eine [http://de.wikipedia.org/wiki/Ordnungsrelation Ordnung] (&amp;lt;-Relation oder &amp;gt;-Relation).&lt;br /&gt;
&lt;br /&gt;
Der folgende Python-Code zeigt beispielhaft, wie man in einem Suchbaum suchen könnte. Der Konstruktor für einen Knoten des Suchbaums ließe sich zum Beispiel so implementieren:&lt;br /&gt;
 &lt;br /&gt;
 class Node:&lt;br /&gt;
     def __init__(self, key):&lt;br /&gt;
         self.key = key&lt;br /&gt;
         self.left = self.right = None&lt;br /&gt;
 &lt;br /&gt;
 root = ...    # Wurzel des Suchbaums&lt;br /&gt;
 nodeFound = treeSearch(root, key)   # None, falls nichts gefunden&lt;br /&gt;
 &lt;br /&gt;
 def treeSearch(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return None&lt;br /&gt;
     elif node.key == key:&lt;br /&gt;
         return node&lt;br /&gt;
     elif key &amp;lt; node.key:&lt;br /&gt;
         return treeSearch(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         return treeSearch(node.right, key)&lt;br /&gt;
&lt;br /&gt;
Daraus resultiert der folgende Suchalgorithmus:&lt;br /&gt;
&lt;br /&gt;
 def treeSort(node,array):     # dynamisches Array als 2. Argument&lt;br /&gt;
     if node is None:     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
         return None:&lt;br /&gt;
     treeSort(node.left, array)     # rekursiv&lt;br /&gt;
     array.append(node.key)     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
     treeSort(node.right, array)     # rekursiv&lt;br /&gt;
&lt;br /&gt;
Komplexität:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 f(N)=\mathcal{O}(1)+f(N_\mathrm{left})+f(N_\mathrm{right})=\mathcal{O}(1)+\mathcal{O}(1)+f(N_\mathrm{leftleft})+f(N_\mathrm{leftright})+\mathcal{O}(1)+f(N_\mathrm{rightleft})&lt;br /&gt;
 +f(N_\mathrm{leftright})=N\ast\mathcal{O}(1)=\mathcal{O}(N)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sortier-Pseudocode:&lt;br /&gt;
&lt;br /&gt;
 Sortieren:&lt;br /&gt;
     (Array) a     # unsortiert&lt;br /&gt;
     (tree) t     # zunächst leer&lt;br /&gt;
 (dynamisches Array) r     # später sortiert&lt;br /&gt;
 for e in a:&lt;br /&gt;
     t = treeInsert(t, e)&lt;br /&gt;
 treeSort(t, r)&lt;br /&gt;
&lt;br /&gt;
== Insert ==&lt;br /&gt;
&lt;br /&gt;
Was passiert wenn der key (Schlüssel) schon vorhanden ist?&lt;br /&gt;
* error (exception)&lt;br /&gt;
* nichts einfügen&lt;br /&gt;
* nichts einfügen aber einen boolean return zurückgeben (einfügen=true, false)&lt;br /&gt;
* nochmals eingefügt (z.B. in der Node Klasse)&lt;br /&gt;
&lt;br /&gt;
Wobei die ersten 3 Punkte zur Mengensemantik gehören und der letzte eine Multimenge ist.&lt;br /&gt;
&lt;br /&gt;
Algorithmus im zweiten Fall (nichts einfügen):&lt;br /&gt;
&lt;br /&gt;
 def treeInsert(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return Node(key)     # Alternative Schreibweise: node = Node(key)&lt;br /&gt;
     if node.key == key:      # und dann:&lt;br /&gt;
         return node          # pass&lt;br /&gt;
     elif key &amp;lt; node.key:     # links im Baum&lt;br /&gt;
         node.left = treeInsert(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         node.right = treeInsert(node.right, key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Remove ==&lt;br /&gt;
Fälle:&lt;br /&gt;
# key (bzw. Knoten der key enthält) ist ein Blatt =&amp;gt; einfach löschen&lt;br /&gt;
# node hat &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; linken Unterbaum oder &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; rechten Unterbaum =&amp;gt; durch Unterbaum ersetzen&lt;br /&gt;
# node hat beide Unterbäume:&lt;br /&gt;
#* Suche Vorgänger: max k &amp;lt; key (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; keys); Vorgänger ist immer ein Fall 1 oder Fall 2&lt;br /&gt;
#*:=&amp;gt; ersetze node durch Vorgänger und entferne Vorgänger&lt;br /&gt;
&lt;br /&gt;
 def treePredecessor(node):     # wird nur bei Fall 3 aufgerufen&lt;br /&gt;
     node = node.left&lt;br /&gt;
     while node.right is not None:&lt;br /&gt;
         node = node.right&lt;br /&gt;
     return node&lt;br /&gt;
 &lt;br /&gt;
 def treeRemove(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return node&lt;br /&gt;
     if key &amp;lt; node.key:&lt;br /&gt;
         node.left = treeRemove(node.left, key)&lt;br /&gt;
     elif key &amp;gt; node.key:&lt;br /&gt;
         node.right = treeRemove(node.right, key)&lt;br /&gt;
     else:&lt;br /&gt;
         if node.left is None and node.right is None:     # Fall 1&lt;br /&gt;
             node = None&lt;br /&gt;
         elif node.left is None:     # Fall 2&lt;br /&gt;
             node = node.right       # +&lt;br /&gt;
         elif node.right is None:    # Fall 2&lt;br /&gt;
             node = node.left&lt;br /&gt;
         else:&lt;br /&gt;
             pred = treePredecessor(node)&lt;br /&gt;
             node.key = pred.key&lt;br /&gt;
             node.left = treeRemove(node.left, pred.key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Komplexitätsanalyse ==&lt;br /&gt;
&lt;br /&gt;
* Pfad (Zwischen node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;): &lt;br /&gt;
**Folge von Knoten (node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt;,...,node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt;), sodass:&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;ki&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;ki+1&amp;lt;/sub&amp;gt; haben eine gemeinsame Kante.&lt;br /&gt;
[[Image:Baum_Pfad.png]]&lt;br /&gt;
(Ein Baum ist ein Graph, indem es zwischen beliebigen Knoten stets genau einen Pfad gibt.)&lt;br /&gt;
&lt;br /&gt;
* Länge eines Pfades: Anzahl der Kanten = Anzahl der Knoten - 1&lt;br /&gt;
* Tiefe eines Knotens: Pfadlänge vom Knoten zur Wurzel des Baumes.&lt;br /&gt;
* Tiefe des Baumes: maximale Tiefe eines Knotens&lt;br /&gt;
* Balance eines Baumes: maximale Tiefe(k) - minimale Tiefe(k) (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; Blätter)&lt;br /&gt;
&lt;br /&gt;
== Ungünstigster Fall von treeSearch ==&lt;br /&gt;
&lt;br /&gt;
Komplexität von treeSearch = Länge des Pfades zum Knoten wo &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; gefunden wird, oder erkannt wird, dass &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; nicht im Baum ist.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; wird nicht gefunden, aber für diese Entscheidung muss der Längste Pfad vollständig durchlaufen werden.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(T)&amp;lt;/math&amp;gt; wo T = Tiefe des Baumes&lt;br /&gt;
*: [1,2,3,4,5]:&amp;lt;br /&amp;gt; [[Image:Balance.png]]&lt;br /&gt;
*: Tiefe T = 4, Balance = 4&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; wo N = Anzahl der Elemente.&lt;br /&gt;
&lt;br /&gt;
== Aufgabe ==&lt;br /&gt;
&lt;br /&gt;
Minimiere Balance (erzeuge balancierten Baum):&lt;br /&gt;
# Einfügen in geschickter Reihenfolge (siehe Übungsaufgabe)&lt;br /&gt;
# Selbstbalancierter Baum:&lt;br /&gt;
#* Überprüfen der Balance nach jedem Einfügen&lt;br /&gt;
#* Umstrukturieren des Baumes, falls Balance &amp;gt; 1 (Suchbaum-Bedingung muss erhalten bleiben)&lt;br /&gt;
#* AVL-Bäume (älteste Variante)&lt;br /&gt;
#* Rot-Schwarz-Bäume (verbreiteste Variante)&lt;br /&gt;
#* Treaps (flexibelste Variante, siehe Übung)&lt;br /&gt;
#* Splay trees&lt;br /&gt;
#* Andersson Trees (einfachste Variante)&lt;br /&gt;
(#* Skip Lists (schnellste Variante, aber kein Binärbaum))&lt;br /&gt;
&lt;br /&gt;
== Umstrukturieren, so dass Suchbaumbedingung erhalten bleibt: ==&lt;br /&gt;
&lt;br /&gt;
Rotation: elementare Umstrukturierungen&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum_Rotation.png]]&lt;br /&gt;
&lt;br /&gt;
 def rotateRight(node):&lt;br /&gt;
     newRoot = node.left&lt;br /&gt;
     node.left = newRoot.right&lt;br /&gt;
     newRoot.right = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
 def rotateLeft(node):&lt;br /&gt;
     newRoot = node.right&lt;br /&gt;
     node.right = newRoot.left&lt;br /&gt;
     newRoot.left = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
== Balance eines Suchbaumes ==&lt;br /&gt;
&lt;br /&gt;
===Balance eines Baumes zu  definieren:===&lt;br /&gt;
[[Image:Abbildung1.jpg|250px|right]]&lt;br /&gt;
*betrachte &amp;lt;tt&amp;gt;None&amp;lt;/tt&amp;gt; als zusätzlichen Knoten : '''sentinel'''   ([[englische Sprache|engl.]] für ''Wächter'')&lt;br /&gt;
*oder defeniere speziellen sentinel-Knoten&lt;br /&gt;
*RS - Pfades : von &amp;lt;tt&amp;gt;root&amp;lt;/tt&amp;gt; &amp;amp;rarr; ''sentinel''&lt;br /&gt;
*Länge eines Pfades ''P''      '''''|P|'''''&lt;br /&gt;
* &amp;lt;u&amp;gt;die Balance eines Baumes zu defenieren:&amp;lt;/u&amp;gt;&lt;br /&gt;
: '''''B = max P '''''&amp;lt;math&amp;gt; \in &amp;lt;/math&amp;gt; '''''{RS}    |P| - min |P|'''''&lt;br /&gt;
:'''''{RS}'''''  Menge aller RS-Pfade&lt;br /&gt;
;vollständiger Baum:  Balance &amp;lt;math&amp;gt;~=0&amp;lt;/math&amp;gt;&lt;br /&gt;
:Alle Knoten(außer Blättern) haben 2 Kinder.&lt;br /&gt;
;perfect balancierter Baum:  Balance  &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 1&lt;br /&gt;
'''alternative Defenition für perfect balancierte Bäume'''&lt;br /&gt;
:Für jeden Teilbaum gilt es: rechtes und linkes Kind (für jeden Knoten) sind auch wieder perfect balancierte Bäume und ihre Höhe unterscheidet sich höchstens um '''1'''.&lt;br /&gt;
&lt;br /&gt;
===Größe===&lt;br /&gt;
[[Image:baum-v.png|700px|right]]&lt;br /&gt;
====Größe des vollständigen Baumes====&lt;br /&gt;
Ebene K hat 2 &amp;lt;sup&amp;gt;k&amp;lt;/sup&amp;gt; Knoten.&lt;br /&gt;
Falls Tiefe = d , dann N = 2&amp;lt;sup&amp;gt;0&amp;lt;/sup&amp;gt; + 2&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;.....+ 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; = 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1&lt;br /&gt;
&lt;br /&gt;
====Größe des perfect balancierten Baumes====&lt;br /&gt;
Die Tiefe d, kann nicht besser sein als vollständiger Baum&lt;br /&gt;
:'''=&amp;gt;'''N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2 &amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
;schlechteste perfect balancierter Baum : ist ein vollständiger der Tiefe  d - 1 + 1, N &amp;lt;math&amp;gt; \ge&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:'''=&amp;gt;'''  2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1)   &amp;lt;math&amp;gt;~&amp;lt;&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt;)&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
d &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt;  log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; (d + 1)&lt;br /&gt;
&lt;br /&gt;
*Ergibt die Komplexität der Suche im schlechtesten Fall: Anzahl der Vergleiche pro Knoten( = 2 bzw. = 3)&amp;lt;math&amp;gt;\ast&amp;lt;/math&amp;gt;Anzahl der Knoten&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow ~f(N)\le 2d \le 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
*'''perfekt balancierter Baum '''=&amp;gt; AVL-Baum &lt;br /&gt;
*'''balancierter Baum''':&lt;br /&gt;
::&amp;lt;math&amp;gt;\forall  N:d(N)\le c \ast d (N)&amp;lt;/math&amp;gt; und  &amp;lt;math&amp;gt;1 \le c &amp;lt; \mathcal {1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:: d ist die Tiefe von perfekt balancierten Baum&lt;br /&gt;
&lt;br /&gt;
:''Komplexität der Suche'':&amp;lt;math&amp;gt; ~f(N)\le  c\ast 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
:algorithmisch einfacher als perfekt balancierter Baum, aber fast genauso schnell&lt;br /&gt;
&lt;br /&gt;
====Selbst-balancierter Baum====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  def insertTree(node,key):&lt;br /&gt;
    if node is sentinel:       #(None = sentinel)&lt;br /&gt;
        return Node(key)       &lt;br /&gt;
    if node.key == key:&lt;br /&gt;
        return Node&lt;br /&gt;
    if key &amp;lt; node.key:&lt;br /&gt;
        Node = insertTree(node.left, key)&lt;br /&gt;
    else:&lt;br /&gt;
        node.right = insertTree(node.right, key)&lt;br /&gt;
   #optimiere Balance:&lt;br /&gt;
        return node&lt;br /&gt;
&lt;br /&gt;
===Anderson-Bäume===&lt;br /&gt;
&lt;br /&gt;
  class Node:&lt;br /&gt;
    def__init__(self, key):&lt;br /&gt;
        self.key = key&lt;br /&gt;
        self.left = selft.right = None&lt;br /&gt;
        #einfügen:&lt;br /&gt;
        self.level = 1&lt;br /&gt;
&lt;br /&gt;
:''level'' : kodiert Abstand von sentinel&lt;br /&gt;
====Regeln====&lt;br /&gt;
Es gibt vertikale Kanten(parent.level == child.level + 1 ) und horizontale Kanten(parent.level == child.level)&lt;br /&gt;
:*verfeinerter Regel :  ''level kodiert reduzierten Abstand von Setinel''  (d.b. horizontale Kanten werden nicht gezählt)&lt;br /&gt;
::(die nächste zwei Regeln sichern die Balance):&lt;br /&gt;
::*alle RS-Pfade haben die gleiche reduzierte Länge oder ''root'' hat bei allen Pfaden das gleiche Level&lt;br /&gt;
::*kein Pfad hat 2 aufeinanderer folgende horizontale Kanten&lt;br /&gt;
:*nur Kanten zum rechten Kind dürfen horizontal sein&lt;br /&gt;
:*die reduzierte Höhe jedes Blatts ist '''hr=1'''&lt;br /&gt;
&lt;br /&gt;
====Beweis====&lt;br /&gt;
Vereinfachung des Algorithmus&lt;br /&gt;
:'''Satz''':ein Anderson-Baum ist balanciert.&lt;br /&gt;
*1. Sei '''hr'''- die reduzierte Höhe&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; jeder Teilbaum enthält mindestens &amp;lt;math&amp;gt;N\ge 2^{hr} -1 &amp;lt;/math&amp;gt;  Knoten&lt;br /&gt;
&lt;br /&gt;
::a). Blätter : reduzierte Höhe 1 =&amp;gt; &amp;lt;math&amp;gt; N\ge 2^{1} - 1 = 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
::b). inneren Knoten: jeder Unterbaum hat mindestens reduzierte Höhe &amp;lt;math&amp;gt; ~hr - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt;  jeder Unterbaum hat mindestens &amp;lt;math&amp;gt; ~2^{hr} -1 &amp;lt;/math&amp;gt; Knoten&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt;N \ge 2 (2^{hr-1} -1)+1 = 2^w - 2 + 1 = 2^{hr} -1&amp;lt;/math&amp;gt;&lt;br /&gt;
::::alle RS-Pfade heben gleiche Länge&lt;br /&gt;
[[Image:Anderson.png|700px|right]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
*2. Kein Pfad hat 2 aufeinanderfolgende horizontal Kanten&lt;br /&gt;
:::&amp;lt;math&amp;gt;\Rightarrow  d \le 2 hr + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
*3. zusammen:&lt;br /&gt;
:&amp;lt;math&amp;gt;N \ge 2^{\frac {d}{2}} - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;log_{2}{ N}+1\ge log 2^{\frac {d}{2}} - 1 \ge \frac {d}{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; ~d &amp;lt; 2 log_{2}{(N + 1)}&amp;lt;/math&amp;gt;     - balancierter Baum&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
:'''Suchzeit''' : &amp;lt;math&amp;gt; ~ f(N) = \mathcal{O}(log{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Wie erreicht man die Balance===&lt;br /&gt;
====Rotation====&lt;br /&gt;
[[Image:Abbildung4.jpg|350px|Illustration von Rotation]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
   def rotateRight (node):&lt;br /&gt;
      root = node.left&lt;br /&gt;
      node.left = node.right&lt;br /&gt;
      root.right = node&lt;br /&gt;
      return root&lt;br /&gt;
   def rotateLeft(node):&lt;br /&gt;
      root = node.right&lt;br /&gt;
      node.right = node.left&lt;br /&gt;
      root.left = node&lt;br /&gt;
      return root&lt;br /&gt;
&lt;br /&gt;
====Optimierung der Balance====&lt;br /&gt;
   &lt;br /&gt;
&lt;br /&gt;
       if node.left is not sentinel and node.level==node.left.level:&lt;br /&gt;
            node &amp;gt; rotateRight(node)&lt;br /&gt;
       if node.right is not sentinel and node.right.right is  not sentinel and node.level==rotate.right.right.level:&lt;br /&gt;
            node = rotateLeft(node)&lt;br /&gt;
            node.level += 1&lt;/div&gt;</summary>
		<author><name>Nata</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1491</id>
		<title>Suchen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1491"/>
				<updated>2008-05-29T00:15:41Z</updated>
		
		<summary type="html">&lt;p&gt;Nata: /* Rotation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Es wäre super wenn jemand ganz kurz schreiben könnte, was am Donnerstag zu den Funktionen treeInsert/Remove/HasKey gemacht wurde, was man ja für den aktuellen Übungszettel braucht. Danke :-)&amp;lt;br /&amp;gt;&lt;br /&gt;
/edit: an eine Funktion &amp;quot;HasKey&amp;quot; kann ich mich aus der Vorlesung nicht erinnern. Wenn die jemand hat, bitte eintragen. - Protokollant&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--Hallo, ich kenne leider deinen Namen nicht, ich habe den Wiki-Eintrag von letztem Donnerstag gemacht.Da das Thema 'Dynamisches Array' noch Thema von letzter Woche war, hab ich die Wiederholung der amortisierten Kosten von diesem Mittwoch noch bei Effizienz eingetragen, du musst es also nicht noch mal in deinem Wiki eintragen. Du kannst dir aber gerne mal anschauen was ich geschrieben habe, vielleicht fallen dir noch ein paar Sachen ein, die man hätte besser machen können. lg, Franziska.--&amp;gt;&lt;br /&gt;
Das Suchen ist eine grundlegende Operation in der Informatik. Viele Probleme in der Informatik können auf Suchaufgaben zurückgeführt werden.&lt;br /&gt;
&lt;br /&gt;
Gemeint ist mit Suchen das Wiederauffinden einer oder mehrerer Datensätze aus einer Menge von früher gespeicherten Datensätzen. Ein paar einleitende Worte zum Suchproblem findet man [http://de.wikipedia.org/wiki/Suche hier].&lt;br /&gt;
&lt;br /&gt;
== Überblick verschiedener Suchmethoden ==&lt;br /&gt;
&lt;br /&gt;
Um sich der Vielseitigkeit von Suchproblemen bewusst zu werden, ist es sinnvoll, sich einen Überblick über verschiedene Suchmethoden zu verschaffen. &lt;br /&gt;
&lt;br /&gt;
Hier sei auch auf einen bereits existierenden Wikipedia-Artikel zu [http://de.wikipedia.org/wiki/Suchverfahren Suchverfahren] verwiesen.&lt;br /&gt;
&lt;br /&gt;
Allen gemeinsam ist die grundlegende Aufgabe, ein Datenelement mit bestimmten Eigenschaften aus einer großen Menge von Datenelementen zu selektieren.&lt;br /&gt;
Dies kann, natürlich ohne jeden Anspruch auf Vollständigkeit, nach einer der jetzt diskutierten Methoden geschehen:&lt;br /&gt;
&lt;br /&gt;
* '''Schlüsselsuche''': meint das Suchen von Elementen mit bestimmtem Schlüssel; ein klassisches Beispiel wäre das Suchen in einem Wörterbuch,  die Schlüssel entsprechen hier den Wörtern, die Datensätze wären die zu den Wörtern gehörigen Eintragungen.&lt;br /&gt;
&lt;br /&gt;
* '''Bereichssuche''': Im Allgemeinen meint die Bereichssuche in n-Dimensionen die Selektion von Elementen mit Eigenschaften aus einem bestimmten n-dimensionalen Volumen. Im eindimensionalen Fall will man alle Elemente finden, deren Eigenschaft(en) in einem bestimmten Intervall liegen. Die Verallgemeinerung auf n-Dimensionen ist offensichtlich. Ein Beispiel für die Bereichssuche in einer 3D-Kugel wäre ein Handy mit Geolokalisierung, welches alle Restaurants in einem Umkreis von 500m findet. Lineare Ungleichungen werden graphisch durch [http://de.wikipedia.org/wiki/Hyperebene Hyperebenen] repräsentiert. In 2D sind diese Hyperebenen Geraden. Die Ungleichungen können dann den Lösungsraum in irgendeiner Form begrenzen.&lt;br /&gt;
&lt;br /&gt;
* '''Ähnlichkeitssuche''': Finde Elemente, die gegebenen Eigenschaften möglichst ähnlich sind. Ein prominentes Beispiel ist Google (=Ähnlichkeit zwischen Suchbegriffen und Dokumenten) oder das Suchen des nächstengelegenen Restaurants (Ähnlichkeit zwischen eigener Position und Position des Restaurants). Ein wichtigster Spezialfall ist die ''nächste-nachbar Suche''.&lt;br /&gt;
&lt;br /&gt;
* '''Graphensuche''': Hier wäre beispielsweise das Problem optimaler Wege zu nennen (Navigationssuche). Dieser Punkt wird später im Verlauf der Vorlesung noch einmal aufgegriffen werden.&lt;br /&gt;
&lt;br /&gt;
Im jetzt Folgenden wird nur noch die ''Schlüsselsuche'' betrachtet werden.&lt;br /&gt;
&lt;br /&gt;
==Sequentielle Suche==&lt;br /&gt;
&lt;br /&gt;
Die ''sequentielle'' oder ''lineare'' Suche ist die 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 = sequentialSearch(a, key) &lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq &amp;lt;/math&amp;gt; foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def sequentialSearch(a, key):&lt;br /&gt;
    for i in range(len(a)):&lt;br /&gt;
        if a[i] == key:  # bzw. allgemeiner a[i].key == key &lt;br /&gt;
            return i&lt;br /&gt;
    return -1&lt;br /&gt;
&lt;br /&gt;
Wir wollen jetzt die Komplexität dieses Algorithmus bestimmen, wobei die Problemgröße durch &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt; gegeben ist. &lt;br /&gt;
&lt;br /&gt;
Dabei nimmt man an, dass der innerste Vergleich (a[i] == key) jeweils &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt; ist (diese Annahme könnte verletzt sein, wenn der Vergleichsoperator überladen ist und dadurch eine höhere Komplexität hat). Dieser Vergleich wird in der for-Schleife jeweils N-mal durchgeführt (&amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt;), so dass man nach der Verschachtelungsregel eine gesamte Komplexität von &amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt; erhält.&lt;br /&gt;
&lt;br /&gt;
Der Name ''lineare'' Suche rührt von diesem linearen Anwachsen der Komplexität mit der Arraygröße her.&lt;br /&gt;
&lt;br /&gt;
==Binäre Suche==&lt;br /&gt;
&lt;br /&gt;
Wie wir weiter unten zeigen werden, gestattet es diese Suchmethode, die Gesamtdauer der Suche in großen Datensätzen beträchtlich zu verringern. Die Methode beruht auf dem [http://de.wikipedia.org/wiki/Divide_and_Conquer Divide and Conquer-Prinzip], wobei die Suche in jedem Schritt rekursiv auf eine Hälfte des Datensatzes eingeschränkt wird. Weitere Details zur Methode sind [http://de.wikipedia.org/wiki/Bin%C3%A4re_Suche hier] zu finden. &lt;br /&gt;
&lt;br /&gt;
Die Methode ist nur dann anwendbar beziehungsweise effektiv, wenn folgendes gilt:&lt;br /&gt;
&lt;br /&gt;
# Auf der Eigenschaft der Daten, die zur Suche verwendet wird, ist eine Ordnung im Sinne von &amp;lt; oder &amp;gt; definiert.&lt;br /&gt;
# Wir wollen uns auf Datensätze beschränken, die schon fertig aufgebaut sind, in die also keine neuen Elemente mehr eingefügt werden, wenn man mit dem Suchen beginnt. Ist dies nicht der Fall, so ist unter Umständen die Implementierung über einen [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum Binärbaum] (siehe auch weiter unten) geschickter. &lt;br /&gt;
&lt;br /&gt;
Der folgende Algorithmus zeigt eine beispielhafte Implementierung der Methode (wir setzen wieder &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt;):&lt;br /&gt;
&lt;br /&gt;
 a = [...,...]     # array&lt;br /&gt;
 a.sort()   # sortiere über Ordnung des Schlüssels&lt;br /&gt;
 foundIndex = binSearch(a, key, 0, len(a))  # (Array, Schlüssel, von wo bis wo suchen im Array)&lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq&amp;lt;/math&amp;gt;  foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def binSearch(a, key, start, end):  # start ist 1. Index, end ist letzter Index + 1&lt;br /&gt;
    size = end - start   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if size &amp;lt;= 0:   # Bereich leer?  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return -1   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    center = (start + end)/2   # Integer Division, Ergebnis wird abgerundet, wichtig für ganzzahlige Indizes &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if a[center] == key:  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return center  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    elif a[center] &amp;lt; key:  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return binSearch(a, key, center + 1, end)  # Rekursion&lt;br /&gt;
    else:&lt;br /&gt;
        return binSearch(a, key, start + 1, center)  # Rekursion&lt;br /&gt;
&lt;br /&gt;
Zur Berechnung der Komplexität dieses Algorithmus vernachlässigen wir zunächst den Aufwand, den die Sortierung weiter oben verursacht. Dieser Schritt mag oder mag nicht zulässig sein.&lt;br /&gt;
&lt;br /&gt;
Nach der Sequenzregel haben auch alle &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;.  Es bleibt die Komplexität der Rekursion zu berechnen. Die gesamte Komplexität des Algorithmus (jetzt als Funktion f bezeichnet) setzt sich zusammen aus den oben erwähnten &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen sowie der Rekursion und ist &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f(N) = \mathcal{O}(1) + f(N/2) = \mathcal{O}(1) + \mathcal{O}(1) + f(N/4) = ... = \underbrace{\mathcal{O}(1) + ... + \mathcal{O}(1) + \underbrace{f(0)}_{\mathcal{O}(1)\, \rightarrow \,\mathrm{size-Abfrage}}}_{n+1 \,\mathrm{Terme}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Falls jetzt gilt &amp;lt;math&amp;gt; N = 2^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \rightarrow f(N) = \mathcal{O}(1) \cdot \mathcal{O}(n+1) = \mathcal{O}(n) = \mathcal{O}(\lg N) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Für große Datenmengen ist die ''binäre Suche'' also weit effizienter als die ''lineare Suche''. Verdoppelt sich beispielsweise die zu durchsuchende Datenmenge, so verdoppelt sich der Aufwand für die ''sequentielle Suche'' - bei der ''binären Suche'' hingegen benötigt man lediglich eine zusätzliche Vergleichsoperation. &lt;br /&gt;
&lt;br /&gt;
Für kleine Daten (&amp;lt;math&amp;gt; N = 4,\, 5 &amp;lt;/math&amp;gt;) ist die ''sequentielle Suche'' jedoch schneller als die ''binäre Suche'', da hier die rekursiven Funktionsaufrufe teurer als das Mehr an Vergleichen sind. Ein anderer ungünstiger Fall ist gegeben, wenn nur sehr wenige Suchanfragen erfolgen (weniger als &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; viele). Dann wird der Aufwand durch das Sortieren des Arrays dominiert, ist also &amp;lt;math&amp;gt;\mathcal{O}(N \lg N) &amp;lt;/math&amp;gt;. Auch dann ist sequentielle Suche vorzuziehen.&lt;br /&gt;
&lt;br /&gt;
Eine relativ einfache Möglichkeit, die ''binäre Suche'' zu verbessern, ist die sogenannte ''Interpolationssuche''. Hierbei wird die neue Position für die Suche, also die Mitte des Arrays, durch eine Schätzung ersetzt, die angibt, wo sich der Schlüssel innerhalb des Arrays befinden könnte. Bei der Suche in einem Telefonbuch nach dem Namen Zebra würde man ja auch nicht in der Mitte anfangen. Näheres hierzu im Buch von ''Sedgewick''.&lt;br /&gt;
&lt;br /&gt;
Um sich den Algorithmus der ''binären Suche'' klar zu machen, ist es instruktiv, sich die folgende Tabelle genauer anzusehen, die die sukzessive Belegung der Variablen bei verschiedenen Anfragen beschreibt. Die Testfälle wurden nach dem Prinzip des ''domain partitioning'' gewählt. Das zugehörige Array hat die Einträge&lt;br /&gt;
&lt;br /&gt;
 a = [2, 3, 4, 5, 6]&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align:center&amp;quot; border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;5&amp;quot; cellspacing=&amp;quot;0&amp;quot; &lt;br /&gt;
! gesuchter key   !!  start      !! end  !! size !! center !! return &amp;lt;br/&amp;gt; (-1 oder index)  !! Kommentare  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 4     ||0            || 5    ||  5   || 2   ||  2         || gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 2     || 0           || 5    ||  5   || 2   ||            || linker Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||           &lt;br /&gt;
|-&lt;br /&gt;
|       ||  0          || 1    ||  1   || 0   ||  0         || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 1     ||0            || 5    ||  5   || 2   ||            || links außerhalb&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 1    ||  1   || 0   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 0    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 6     ||0            || 5    ||  5   || 2   ||            || rechter Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    || 2    || 4   || 4          || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 5     ||0            || 5    ||  5   || 2   ||            || typischer Fall&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||3            || 5    ||  2   || 4   ||            ||  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       || 3           || 4    ||  1   || 3   || 3          || gefunden&lt;br /&gt;
|- &lt;br /&gt;
| 7     ||0            || 5    ||  5   || 2   ||            || rechts außerhalb        &lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    ||  2   || 4   ||            ||&lt;br /&gt;
|-&lt;br /&gt;
|       ||5            || 5    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Suche in einem Binärbaum ==&lt;br /&gt;
&lt;br /&gt;
Eine kurze Einführung in Binärbäume findet man [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum hier].&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum.png|text-top|300x300px|Zur Illustration von Bäumen]]&lt;br /&gt;
&lt;br /&gt;
Bäume sind zweidimensional verkettete Strukturen. Sie gehören zu den fundamentalen Datenstrukturen in der Informatik. Da man in Bäumen nicht nur Daten speichern kann, sondern auch relevante Beziehungen der Daten untereinander, festgelegt über eine Ordnung auf der vergleichenden Dateneigenschaft (''Schlüssel''), eignen sich Bäume also insbesondere, um gesuchte Daten schnell wieder auffinden zu können.&lt;br /&gt;
&lt;br /&gt;
Ein ''Binärbaum'' wie oben skizziert besteht aus einer Menge von ''Knoten'', die untereinander durch ''Kanten'' verbunden sind. Jeder Knoten hat einen linken und einen rechten Unterbaum, der auch leer sein kann (in Python ließe sich dies mit ''None'' implementieren). Führt eine Kante von Knoten A zu Knoten B, so heißt A Vater von B und B Kind von A. Es gibt genau einen Knoten ohne Vater, den man ''Wurzel'' nennt. Knoten ohne Kinder heißen ''Blätter''.&lt;br /&gt;
&lt;br /&gt;
Ein ''Suchbaum'' hat zusätzlich die Eigenschaft, dass die Schlüssel jedes Knotens sortiert sind. Alle Schlüssel im linken Unterbaum sind kleiner, alle Schlüssel im rechten Unterbaum sind größer als ihr Vater. Wir wollen hierbei annehmen, dass jeder Schlüssel pro Datensatz nur einmal vorkommt, da sich sonst die &amp;gt;- oder &amp;lt;-Relation nicht mehr strikt erfüllen ließe.&lt;br /&gt;
&lt;br /&gt;
Um in einem Baum suchen zu können, wollen wir von zwei Annahmen ausgehen:&lt;br /&gt;
# Einfügen und Suchen im Baum wechseln sich ab. (Wenn das Suchen erst beginnt, nachdem alle Einfügungen erfolgt sind, wäre ein dynamisches Array mit binärer Suche wie oben wesentlich einfacher.)&lt;br /&gt;
# Der Schlüssel, der die Anordnung bestimmt, kennt eine [http://de.wikipedia.org/wiki/Ordnungsrelation Ordnung] (&amp;lt;-Relation oder &amp;gt;-Relation).&lt;br /&gt;
&lt;br /&gt;
Der folgende Python-Code zeigt beispielhaft, wie man in einem Suchbaum suchen könnte. Der Konstruktor für einen Knoten des Suchbaums ließe sich zum Beispiel so implementieren:&lt;br /&gt;
 &lt;br /&gt;
 class Node:&lt;br /&gt;
     def __init__(self, key):&lt;br /&gt;
         self.key = key&lt;br /&gt;
         self.left = self.right = None&lt;br /&gt;
 &lt;br /&gt;
 root = ...    # Wurzel des Suchbaums&lt;br /&gt;
 nodeFound = treeSearch(root, key)   # None, falls nichts gefunden&lt;br /&gt;
 &lt;br /&gt;
 def treeSearch(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return None&lt;br /&gt;
     elif node.key == key:&lt;br /&gt;
         return node&lt;br /&gt;
     elif key &amp;lt; node.key:&lt;br /&gt;
         return treeSearch(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         return treeSearch(node.right, key)&lt;br /&gt;
&lt;br /&gt;
Daraus resultiert der folgende Suchalgorithmus:&lt;br /&gt;
&lt;br /&gt;
 def treeSort(node,array):     # dynamisches Array als 2. Argument&lt;br /&gt;
     if node is None:     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
         return None:&lt;br /&gt;
     treeSort(node.left, array)     # rekursiv&lt;br /&gt;
     array.append(node.key)     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
     treeSort(node.right, array)     # rekursiv&lt;br /&gt;
&lt;br /&gt;
Komplexität:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 f(N)=\mathcal{O}(1)+f(N_\mathrm{left})+f(N_\mathrm{right})=\mathcal{O}(1)+\mathcal{O}(1)+f(N_\mathrm{leftleft})+f(N_\mathrm{leftright})+\mathcal{O}(1)+f(N_\mathrm{rightleft})&lt;br /&gt;
 +f(N_\mathrm{leftright})=N\ast\mathcal{O}(1)=\mathcal{O}(N)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sortier-Pseudocode:&lt;br /&gt;
&lt;br /&gt;
 Sortieren:&lt;br /&gt;
     (Array) a     # unsortiert&lt;br /&gt;
     (tree) t     # zunächst leer&lt;br /&gt;
 (dynamisches Array) r     # später sortiert&lt;br /&gt;
 for e in a:&lt;br /&gt;
     t = treeInsert(t, e)&lt;br /&gt;
 treeSort(t, r)&lt;br /&gt;
&lt;br /&gt;
== Insert ==&lt;br /&gt;
&lt;br /&gt;
Was passiert wenn der key (Schlüssel) schon vorhanden ist?&lt;br /&gt;
* error (exception)&lt;br /&gt;
* nichts einfügen&lt;br /&gt;
* nichts einfügen aber einen boolean return zurückgeben (einfügen=true, false)&lt;br /&gt;
* nochmals eingefügt (z.B. in der Node Klasse)&lt;br /&gt;
&lt;br /&gt;
Wobei die ersten 3 Punkte zur Mengensemantik gehören und der letzte eine Multimenge ist.&lt;br /&gt;
&lt;br /&gt;
Algorithmus im zweiten Fall (nichts einfügen):&lt;br /&gt;
&lt;br /&gt;
 def treeInsert(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return Node(key)     # Alternative Schreibweise: node = Node(key)&lt;br /&gt;
     if node.key == key:      # und dann:&lt;br /&gt;
         return node          # pass&lt;br /&gt;
     elif key &amp;lt; node.key:     # links im Baum&lt;br /&gt;
         node.left = treeInsert(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         node.right = treeInsert(node.right, key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Remove ==&lt;br /&gt;
Fälle:&lt;br /&gt;
# key (bzw. Knoten der key enthält) ist ein Blatt =&amp;gt; einfach löschen&lt;br /&gt;
# node hat &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; linken Unterbaum oder &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; rechten Unterbaum =&amp;gt; durch Unterbaum ersetzen&lt;br /&gt;
# node hat beide Unterbäume:&lt;br /&gt;
#* Suche Vorgänger: max k &amp;lt; key (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; keys); Vorgänger ist immer ein Fall 1 oder Fall 2&lt;br /&gt;
#*:=&amp;gt; ersetze node durch Vorgänger und entferne Vorgänger&lt;br /&gt;
&lt;br /&gt;
 def treePredecessor(node):     # wird nur bei Fall 3 aufgerufen&lt;br /&gt;
     node = node.left&lt;br /&gt;
     while node.right is not None:&lt;br /&gt;
         node = node.right&lt;br /&gt;
     return node&lt;br /&gt;
 &lt;br /&gt;
 def treeRemove(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return node&lt;br /&gt;
     if key &amp;lt; node.key:&lt;br /&gt;
         node.left = treeRemove(node.left, key)&lt;br /&gt;
     elif key &amp;gt; node.key:&lt;br /&gt;
         node.right = treeRemove(node.right, key)&lt;br /&gt;
     else:&lt;br /&gt;
         if node.left is None and node.right is None:     # Fall 1&lt;br /&gt;
             node = None&lt;br /&gt;
         elif node.left is None:     # Fall 2&lt;br /&gt;
             node = node.right       # +&lt;br /&gt;
         elif node.right is None:    # Fall 2&lt;br /&gt;
             node = node.left&lt;br /&gt;
         else:&lt;br /&gt;
             pred = treePredecessor(node)&lt;br /&gt;
             node.key = pred.key&lt;br /&gt;
             node.left = treeRemove(node.left, pred.key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Komplexitätsanalyse ==&lt;br /&gt;
&lt;br /&gt;
* Pfad (Zwischen node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;): &lt;br /&gt;
**Folge von Knoten (node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt;,...,node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt;), sodass:&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;ki&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;ki+1&amp;lt;/sub&amp;gt; haben eine gemeinsame Kante.&lt;br /&gt;
[[Image:Baum_Pfad.png]]&lt;br /&gt;
(Ein Baum ist ein Graph, indem es zwischen beliebigen Knoten stets genau einen Pfad gibt.)&lt;br /&gt;
&lt;br /&gt;
* Länge eines Pfades: Anzahl der Kanten = Anzahl der Knoten - 1&lt;br /&gt;
* Tiefe eines Knotens: Pfadlänge vom Knoten zur Wurzel des Baumes.&lt;br /&gt;
* Tiefe des Baumes: maximale Tiefe eines Knotens&lt;br /&gt;
* Balance eines Baumes: maximale Tiefe(k) - minimale Tiefe(k) (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; Blätter)&lt;br /&gt;
&lt;br /&gt;
== Ungünstigster Fall von treeSearch ==&lt;br /&gt;
&lt;br /&gt;
Komplexität von treeSearch = Länge des Pfades zum Knoten wo &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; gefunden wird, oder erkannt wird, dass &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; nicht im Baum ist.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; wird nicht gefunden, aber für diese Entscheidung muss der Längste Pfad vollständig durchlaufen werden.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(T)&amp;lt;/math&amp;gt; wo T = Tiefe des Baumes&lt;br /&gt;
*: [1,2,3,4,5]:&amp;lt;br /&amp;gt; [[Image:Balance.png]]&lt;br /&gt;
*: Tiefe T = 4, Balance = 4&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; wo N = Anzahl der Elemente.&lt;br /&gt;
&lt;br /&gt;
== Aufgabe ==&lt;br /&gt;
&lt;br /&gt;
Minimiere Balance (erzeuge balancierten Baum):&lt;br /&gt;
# Einfügen in geschickter Reihenfolge (siehe Übungsaufgabe)&lt;br /&gt;
# Selbstbalancierter Baum:&lt;br /&gt;
#* Überprüfen der Balance nach jedem Einfügen&lt;br /&gt;
#* Umstrukturieren des Baumes, falls Balance &amp;gt; 1 (Suchbaum-Bedingung muss erhalten bleiben)&lt;br /&gt;
#* AVL-Bäume (älteste Variante)&lt;br /&gt;
#* Rot-Schwarz-Bäume (verbreiteste Variante)&lt;br /&gt;
#* Treaps (flexibelste Variante, siehe Übung)&lt;br /&gt;
#* Splay trees&lt;br /&gt;
#* Andersson Trees (einfachste Variante)&lt;br /&gt;
(#* Skip Lists (schnellste Variante, aber kein Binärbaum))&lt;br /&gt;
&lt;br /&gt;
== Umstrukturieren, so dass Suchbaumbedingung erhalten bleibt: ==&lt;br /&gt;
&lt;br /&gt;
Rotation: elementare Umstrukturierungen&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum_Rotation.png]]&lt;br /&gt;
&lt;br /&gt;
 def rotateRight(node):&lt;br /&gt;
     newRoot = node.left&lt;br /&gt;
     node.left = newRoot.right&lt;br /&gt;
     newRoot.right = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
 def rotateLeft(node):&lt;br /&gt;
     newRoot = node.right&lt;br /&gt;
     node.right = newRoot.left&lt;br /&gt;
     newRoot.left = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
== Balance eines Suchbaumes ==&lt;br /&gt;
&lt;br /&gt;
===Balance eines Baumes zu  definieren:===&lt;br /&gt;
[[Image:Abbildung1.jpg|250px|right]]&lt;br /&gt;
*betrachte &amp;lt;tt&amp;gt;None&amp;lt;/tt&amp;gt; als zusätzlichen Knoten : '''sentinel'''   ([[englische Sprache|engl.]] für ''Wächter'')&lt;br /&gt;
*oder defeniere speziellen sentinel-Knoten&lt;br /&gt;
*RS - Pfades : von &amp;lt;tt&amp;gt;root&amp;lt;/tt&amp;gt; &amp;amp;rarr; ''sentinel''&lt;br /&gt;
*Länge eines Pfades ''P''      '''''|P|'''''&lt;br /&gt;
* &amp;lt;u&amp;gt;die Balance eines Baumes zu defenieren:&amp;lt;/u&amp;gt;&lt;br /&gt;
: '''''B = max P '''''&amp;lt;math&amp;gt; \in &amp;lt;/math&amp;gt; '''''{RS}    |P| - min |P|'''''&lt;br /&gt;
:'''''{RS}'''''  Menge aller RS-Pfade&lt;br /&gt;
;vollständiger Baum:  Balance &amp;lt;math&amp;gt;~=0&amp;lt;/math&amp;gt;&lt;br /&gt;
:Alle Knoten(außer Blättern) haben 2 Kinder.&lt;br /&gt;
;perfect balancierter Baum:  Balance  &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 1&lt;br /&gt;
'''alternative Defenition für perfect balancierte Bäume'''&lt;br /&gt;
:Für jeden Teilbaum gilt es: rechtes und linkes Kind (für jeden Knoten) sind auch wieder perfect balancierte Bäume und ihre Höhe unterscheidet sich höchstens um '''1'''.&lt;br /&gt;
&lt;br /&gt;
===Größe===&lt;br /&gt;
[[Image:baum-v.png|700px|right]]&lt;br /&gt;
====Größe des vollständigen Baumes====&lt;br /&gt;
Ebene K hat 2 &amp;lt;sup&amp;gt;k&amp;lt;/sup&amp;gt; Knoten.&lt;br /&gt;
Falls Tiefe = d , dann N = 2&amp;lt;sup&amp;gt;0&amp;lt;/sup&amp;gt; + 2&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;.....+ 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; = 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1&lt;br /&gt;
&lt;br /&gt;
====Größe des perfect balancierten Baumes====&lt;br /&gt;
Die Tiefe d, kann nicht besser sein als vollständiger Baum&lt;br /&gt;
:'''=&amp;gt;'''N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2 &amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
;schlechteste perfect balancierter Baum : ist ein vollständiger der Tiefe  d - 1 + 1, N &amp;lt;math&amp;gt; \ge&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:'''=&amp;gt;'''  2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1)   &amp;lt;math&amp;gt;~&amp;lt;&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt;)&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
d &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt;  log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; (d + 1)&lt;br /&gt;
&lt;br /&gt;
*Ergibt die Komplexität der Suche im schlechtesten Fall: Anzahl der Vergleiche pro Knoten( = 2 bzw. = 3)&amp;lt;math&amp;gt;\ast&amp;lt;/math&amp;gt;Anzahl der Knoten&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow ~f(N)\le 2d \le 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
*'''perfekt balancierter Baum '''=&amp;gt; AVL-Baum &lt;br /&gt;
*'''balancierter Baum''':&lt;br /&gt;
::&amp;lt;math&amp;gt;\forall  N:d(N)\le c \ast d (N)&amp;lt;/math&amp;gt; und  &amp;lt;math&amp;gt;1 \le c &amp;lt; \mathcal {1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:: d ist die Tiefe von perfekt balancierten Baum&lt;br /&gt;
&lt;br /&gt;
:''Komplexität der Suche'':&amp;lt;math&amp;gt; ~f(N)\le  c\ast 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
:algorithmisch einfacher als perfekt balancierter Baum, aber fast genauso schnell&lt;br /&gt;
&lt;br /&gt;
====Selbst-balancierter Baum====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  def insertTree(node,key):&lt;br /&gt;
    if node is sentinel:       #(None = sentinel)&lt;br /&gt;
        return Node(key)       &lt;br /&gt;
    if node.key == key:&lt;br /&gt;
        return Node&lt;br /&gt;
    if key &amp;lt; node.key:&lt;br /&gt;
        Node = insertTree(node.left, key)&lt;br /&gt;
    else:&lt;br /&gt;
        node.right = insertTree(node.right, key)&lt;br /&gt;
   #optimiere Balance:&lt;br /&gt;
        return node&lt;br /&gt;
&lt;br /&gt;
===Anderson-Bäume===&lt;br /&gt;
&lt;br /&gt;
  class Node:&lt;br /&gt;
    def__init__(self, key):&lt;br /&gt;
        self.key = key&lt;br /&gt;
        self.left = selft.right = None&lt;br /&gt;
        #einfügen:&lt;br /&gt;
        self.level = 1&lt;br /&gt;
&lt;br /&gt;
:''level'' : kodiert Abstand von sentinel&lt;br /&gt;
====Regeln====&lt;br /&gt;
Es gibt vertikale Kanten(parent.level == child.level + 1 ) und horizontale Kanten(parent.level == child.level)&lt;br /&gt;
:*verfeinerter Regel :  ''level kodiert reduzierten Abstand von Setinel''  (d.b. horizontale Kanten werden nicht gezählt)&lt;br /&gt;
::(die nächste zwei Regeln sichern die Balance):&lt;br /&gt;
::*alle RS-Pfade haben die gleiche reduzierte Länge oder ''root'' hat bei allen Pfaden das gleiche Level&lt;br /&gt;
::*kein Pfad hat 2 aufeinanderer folgende horizontale Kanten&lt;br /&gt;
:*nur Kanten zum rechten Kind dürfen horizontal sein&lt;br /&gt;
:*die reduzierte Höhe jedes Blatts ist '''hr=1'''&lt;br /&gt;
&lt;br /&gt;
====Beweis====&lt;br /&gt;
Vereinfachung des Algorithmus&lt;br /&gt;
:'''Satz''':ein Anderson-Baum ist balanciert.&lt;br /&gt;
*1. Sei '''hr'''- die reduzierte Höhe&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; jeder Teilbaum enthält mindestens &amp;lt;math&amp;gt;N\ge 2^{hr} -1 &amp;lt;/math&amp;gt;  Knoten&lt;br /&gt;
&lt;br /&gt;
::a). Blätter : reduzierte Höhe 1 =&amp;gt; &amp;lt;math&amp;gt; N\ge 2^{1} - 1 = 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
::b). inneren Knoten: jeder Unterbaum hat mindestens reduzierte Höhe &amp;lt;math&amp;gt; ~hr - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt;  jeder Unterbaum hat mindestens &amp;lt;math&amp;gt; ~2^{hr} -1 &amp;lt;/math&amp;gt; Knoten&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt;N \ge 2 (2^{hr-1} -1)+1 = 2^w - 2 + 1 = 2^{hr} -1&amp;lt;/math&amp;gt;&lt;br /&gt;
::::alle RS-Pfade heben gleiche Länge&lt;br /&gt;
[[Image:Anderson.png|700px|right]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
*2. Kein Pfad hat 2 aufeinanderfolgende horizontal Kanten&lt;br /&gt;
:::&amp;lt;math&amp;gt;\Rightarrow  d \le 2 hr + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
*3. zusammen:&lt;br /&gt;
:&amp;lt;math&amp;gt;N \ge 2^{\frac {d}{2}} - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;log_{2}{ N}+1\ge log 2^{\frac {d}{2}} - 1 \ge \frac {d}{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; ~d &amp;lt; 2 log_{2}{(N + 1)}&amp;lt;/math&amp;gt;     - balancierter Baum&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
:'''Suchzeit''' : &amp;lt;math&amp;gt; ~ f(N) = \mathcal{O}(log{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Wie erreicht man die Balance===&lt;br /&gt;
====Rotation====&lt;br /&gt;
[[Image:Abbildung4.jpg|350px|Illustration von Rotation]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
   def rotateRight (node):&lt;br /&gt;
      root = node.left&lt;br /&gt;
      node.left = node.right&lt;br /&gt;
      root.right = node&lt;br /&gt;
      return root&lt;br /&gt;
   def rotateLeft(node):&lt;br /&gt;
      root = node.right&lt;br /&gt;
      node.right = node.left&lt;br /&gt;
      root.left = node&lt;br /&gt;
      return root&lt;br /&gt;
&lt;br /&gt;
====Optimierung der Balance====&lt;br /&gt;
   &lt;br /&gt;
&lt;br /&gt;
       if node.left is not sentinel and node.level==node.left.level:&lt;br /&gt;
            node &amp;gt; rotateRight(node)&lt;br /&gt;
       if node.right is not sentinel and node.right.right is  not sentinel and node.level==rotate.right.right.level:&lt;br /&gt;
            node = rotateLeft(node)&lt;br /&gt;
            node.level += 1&lt;/div&gt;</summary>
		<author><name>Nata</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1490</id>
		<title>Suchen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1490"/>
				<updated>2008-05-29T00:15:22Z</updated>
		
		<summary type="html">&lt;p&gt;Nata: /* Rotation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Es wäre super wenn jemand ganz kurz schreiben könnte, was am Donnerstag zu den Funktionen treeInsert/Remove/HasKey gemacht wurde, was man ja für den aktuellen Übungszettel braucht. Danke :-)&amp;lt;br /&amp;gt;&lt;br /&gt;
/edit: an eine Funktion &amp;quot;HasKey&amp;quot; kann ich mich aus der Vorlesung nicht erinnern. Wenn die jemand hat, bitte eintragen. - Protokollant&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--Hallo, ich kenne leider deinen Namen nicht, ich habe den Wiki-Eintrag von letztem Donnerstag gemacht.Da das Thema 'Dynamisches Array' noch Thema von letzter Woche war, hab ich die Wiederholung der amortisierten Kosten von diesem Mittwoch noch bei Effizienz eingetragen, du musst es also nicht noch mal in deinem Wiki eintragen. Du kannst dir aber gerne mal anschauen was ich geschrieben habe, vielleicht fallen dir noch ein paar Sachen ein, die man hätte besser machen können. lg, Franziska.--&amp;gt;&lt;br /&gt;
Das Suchen ist eine grundlegende Operation in der Informatik. Viele Probleme in der Informatik können auf Suchaufgaben zurückgeführt werden.&lt;br /&gt;
&lt;br /&gt;
Gemeint ist mit Suchen das Wiederauffinden einer oder mehrerer Datensätze aus einer Menge von früher gespeicherten Datensätzen. Ein paar einleitende Worte zum Suchproblem findet man [http://de.wikipedia.org/wiki/Suche hier].&lt;br /&gt;
&lt;br /&gt;
== Überblick verschiedener Suchmethoden ==&lt;br /&gt;
&lt;br /&gt;
Um sich der Vielseitigkeit von Suchproblemen bewusst zu werden, ist es sinnvoll, sich einen Überblick über verschiedene Suchmethoden zu verschaffen. &lt;br /&gt;
&lt;br /&gt;
Hier sei auch auf einen bereits existierenden Wikipedia-Artikel zu [http://de.wikipedia.org/wiki/Suchverfahren Suchverfahren] verwiesen.&lt;br /&gt;
&lt;br /&gt;
Allen gemeinsam ist die grundlegende Aufgabe, ein Datenelement mit bestimmten Eigenschaften aus einer großen Menge von Datenelementen zu selektieren.&lt;br /&gt;
Dies kann, natürlich ohne jeden Anspruch auf Vollständigkeit, nach einer der jetzt diskutierten Methoden geschehen:&lt;br /&gt;
&lt;br /&gt;
* '''Schlüsselsuche''': meint das Suchen von Elementen mit bestimmtem Schlüssel; ein klassisches Beispiel wäre das Suchen in einem Wörterbuch,  die Schlüssel entsprechen hier den Wörtern, die Datensätze wären die zu den Wörtern gehörigen Eintragungen.&lt;br /&gt;
&lt;br /&gt;
* '''Bereichssuche''': Im Allgemeinen meint die Bereichssuche in n-Dimensionen die Selektion von Elementen mit Eigenschaften aus einem bestimmten n-dimensionalen Volumen. Im eindimensionalen Fall will man alle Elemente finden, deren Eigenschaft(en) in einem bestimmten Intervall liegen. Die Verallgemeinerung auf n-Dimensionen ist offensichtlich. Ein Beispiel für die Bereichssuche in einer 3D-Kugel wäre ein Handy mit Geolokalisierung, welches alle Restaurants in einem Umkreis von 500m findet. Lineare Ungleichungen werden graphisch durch [http://de.wikipedia.org/wiki/Hyperebene Hyperebenen] repräsentiert. In 2D sind diese Hyperebenen Geraden. Die Ungleichungen können dann den Lösungsraum in irgendeiner Form begrenzen.&lt;br /&gt;
&lt;br /&gt;
* '''Ähnlichkeitssuche''': Finde Elemente, die gegebenen Eigenschaften möglichst ähnlich sind. Ein prominentes Beispiel ist Google (=Ähnlichkeit zwischen Suchbegriffen und Dokumenten) oder das Suchen des nächstengelegenen Restaurants (Ähnlichkeit zwischen eigener Position und Position des Restaurants). Ein wichtigster Spezialfall ist die ''nächste-nachbar Suche''.&lt;br /&gt;
&lt;br /&gt;
* '''Graphensuche''': Hier wäre beispielsweise das Problem optimaler Wege zu nennen (Navigationssuche). Dieser Punkt wird später im Verlauf der Vorlesung noch einmal aufgegriffen werden.&lt;br /&gt;
&lt;br /&gt;
Im jetzt Folgenden wird nur noch die ''Schlüsselsuche'' betrachtet werden.&lt;br /&gt;
&lt;br /&gt;
==Sequentielle Suche==&lt;br /&gt;
&lt;br /&gt;
Die ''sequentielle'' oder ''lineare'' Suche ist die 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 = sequentialSearch(a, key) &lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq &amp;lt;/math&amp;gt; foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def sequentialSearch(a, key):&lt;br /&gt;
    for i in range(len(a)):&lt;br /&gt;
        if a[i] == key:  # bzw. allgemeiner a[i].key == key &lt;br /&gt;
            return i&lt;br /&gt;
    return -1&lt;br /&gt;
&lt;br /&gt;
Wir wollen jetzt die Komplexität dieses Algorithmus bestimmen, wobei die Problemgröße durch &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt; gegeben ist. &lt;br /&gt;
&lt;br /&gt;
Dabei nimmt man an, dass der innerste Vergleich (a[i] == key) jeweils &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt; ist (diese Annahme könnte verletzt sein, wenn der Vergleichsoperator überladen ist und dadurch eine höhere Komplexität hat). Dieser Vergleich wird in der for-Schleife jeweils N-mal durchgeführt (&amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt;), so dass man nach der Verschachtelungsregel eine gesamte Komplexität von &amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt; erhält.&lt;br /&gt;
&lt;br /&gt;
Der Name ''lineare'' Suche rührt von diesem linearen Anwachsen der Komplexität mit der Arraygröße her.&lt;br /&gt;
&lt;br /&gt;
==Binäre Suche==&lt;br /&gt;
&lt;br /&gt;
Wie wir weiter unten zeigen werden, gestattet es diese Suchmethode, die Gesamtdauer der Suche in großen Datensätzen beträchtlich zu verringern. Die Methode beruht auf dem [http://de.wikipedia.org/wiki/Divide_and_Conquer Divide and Conquer-Prinzip], wobei die Suche in jedem Schritt rekursiv auf eine Hälfte des Datensatzes eingeschränkt wird. Weitere Details zur Methode sind [http://de.wikipedia.org/wiki/Bin%C3%A4re_Suche hier] zu finden. &lt;br /&gt;
&lt;br /&gt;
Die Methode ist nur dann anwendbar beziehungsweise effektiv, wenn folgendes gilt:&lt;br /&gt;
&lt;br /&gt;
# Auf der Eigenschaft der Daten, die zur Suche verwendet wird, ist eine Ordnung im Sinne von &amp;lt; oder &amp;gt; definiert.&lt;br /&gt;
# Wir wollen uns auf Datensätze beschränken, die schon fertig aufgebaut sind, in die also keine neuen Elemente mehr eingefügt werden, wenn man mit dem Suchen beginnt. Ist dies nicht der Fall, so ist unter Umständen die Implementierung über einen [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum Binärbaum] (siehe auch weiter unten) geschickter. &lt;br /&gt;
&lt;br /&gt;
Der folgende Algorithmus zeigt eine beispielhafte Implementierung der Methode (wir setzen wieder &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt;):&lt;br /&gt;
&lt;br /&gt;
 a = [...,...]     # array&lt;br /&gt;
 a.sort()   # sortiere über Ordnung des Schlüssels&lt;br /&gt;
 foundIndex = binSearch(a, key, 0, len(a))  # (Array, Schlüssel, von wo bis wo suchen im Array)&lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq&amp;lt;/math&amp;gt;  foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def binSearch(a, key, start, end):  # start ist 1. Index, end ist letzter Index + 1&lt;br /&gt;
    size = end - start   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if size &amp;lt;= 0:   # Bereich leer?  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return -1   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    center = (start + end)/2   # Integer Division, Ergebnis wird abgerundet, wichtig für ganzzahlige Indizes &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if a[center] == key:  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return center  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    elif a[center] &amp;lt; key:  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return binSearch(a, key, center + 1, end)  # Rekursion&lt;br /&gt;
    else:&lt;br /&gt;
        return binSearch(a, key, start + 1, center)  # Rekursion&lt;br /&gt;
&lt;br /&gt;
Zur Berechnung der Komplexität dieses Algorithmus vernachlässigen wir zunächst den Aufwand, den die Sortierung weiter oben verursacht. Dieser Schritt mag oder mag nicht zulässig sein.&lt;br /&gt;
&lt;br /&gt;
Nach der Sequenzregel haben auch alle &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;.  Es bleibt die Komplexität der Rekursion zu berechnen. Die gesamte Komplexität des Algorithmus (jetzt als Funktion f bezeichnet) setzt sich zusammen aus den oben erwähnten &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen sowie der Rekursion und ist &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f(N) = \mathcal{O}(1) + f(N/2) = \mathcal{O}(1) + \mathcal{O}(1) + f(N/4) = ... = \underbrace{\mathcal{O}(1) + ... + \mathcal{O}(1) + \underbrace{f(0)}_{\mathcal{O}(1)\, \rightarrow \,\mathrm{size-Abfrage}}}_{n+1 \,\mathrm{Terme}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Falls jetzt gilt &amp;lt;math&amp;gt; N = 2^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \rightarrow f(N) = \mathcal{O}(1) \cdot \mathcal{O}(n+1) = \mathcal{O}(n) = \mathcal{O}(\lg N) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Für große Datenmengen ist die ''binäre Suche'' also weit effizienter als die ''lineare Suche''. Verdoppelt sich beispielsweise die zu durchsuchende Datenmenge, so verdoppelt sich der Aufwand für die ''sequentielle Suche'' - bei der ''binären Suche'' hingegen benötigt man lediglich eine zusätzliche Vergleichsoperation. &lt;br /&gt;
&lt;br /&gt;
Für kleine Daten (&amp;lt;math&amp;gt; N = 4,\, 5 &amp;lt;/math&amp;gt;) ist die ''sequentielle Suche'' jedoch schneller als die ''binäre Suche'', da hier die rekursiven Funktionsaufrufe teurer als das Mehr an Vergleichen sind. Ein anderer ungünstiger Fall ist gegeben, wenn nur sehr wenige Suchanfragen erfolgen (weniger als &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; viele). Dann wird der Aufwand durch das Sortieren des Arrays dominiert, ist also &amp;lt;math&amp;gt;\mathcal{O}(N \lg N) &amp;lt;/math&amp;gt;. Auch dann ist sequentielle Suche vorzuziehen.&lt;br /&gt;
&lt;br /&gt;
Eine relativ einfache Möglichkeit, die ''binäre Suche'' zu verbessern, ist die sogenannte ''Interpolationssuche''. Hierbei wird die neue Position für die Suche, also die Mitte des Arrays, durch eine Schätzung ersetzt, die angibt, wo sich der Schlüssel innerhalb des Arrays befinden könnte. Bei der Suche in einem Telefonbuch nach dem Namen Zebra würde man ja auch nicht in der Mitte anfangen. Näheres hierzu im Buch von ''Sedgewick''.&lt;br /&gt;
&lt;br /&gt;
Um sich den Algorithmus der ''binären Suche'' klar zu machen, ist es instruktiv, sich die folgende Tabelle genauer anzusehen, die die sukzessive Belegung der Variablen bei verschiedenen Anfragen beschreibt. Die Testfälle wurden nach dem Prinzip des ''domain partitioning'' gewählt. Das zugehörige Array hat die Einträge&lt;br /&gt;
&lt;br /&gt;
 a = [2, 3, 4, 5, 6]&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align:center&amp;quot; border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;5&amp;quot; cellspacing=&amp;quot;0&amp;quot; &lt;br /&gt;
! gesuchter key   !!  start      !! end  !! size !! center !! return &amp;lt;br/&amp;gt; (-1 oder index)  !! Kommentare  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 4     ||0            || 5    ||  5   || 2   ||  2         || gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 2     || 0           || 5    ||  5   || 2   ||            || linker Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||           &lt;br /&gt;
|-&lt;br /&gt;
|       ||  0          || 1    ||  1   || 0   ||  0         || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 1     ||0            || 5    ||  5   || 2   ||            || links außerhalb&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 1    ||  1   || 0   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 0    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 6     ||0            || 5    ||  5   || 2   ||            || rechter Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    || 2    || 4   || 4          || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 5     ||0            || 5    ||  5   || 2   ||            || typischer Fall&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||3            || 5    ||  2   || 4   ||            ||  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       || 3           || 4    ||  1   || 3   || 3          || gefunden&lt;br /&gt;
|- &lt;br /&gt;
| 7     ||0            || 5    ||  5   || 2   ||            || rechts außerhalb        &lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    ||  2   || 4   ||            ||&lt;br /&gt;
|-&lt;br /&gt;
|       ||5            || 5    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Suche in einem Binärbaum ==&lt;br /&gt;
&lt;br /&gt;
Eine kurze Einführung in Binärbäume findet man [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum hier].&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum.png|text-top|300x300px|Zur Illustration von Bäumen]]&lt;br /&gt;
&lt;br /&gt;
Bäume sind zweidimensional verkettete Strukturen. Sie gehören zu den fundamentalen Datenstrukturen in der Informatik. Da man in Bäumen nicht nur Daten speichern kann, sondern auch relevante Beziehungen der Daten untereinander, festgelegt über eine Ordnung auf der vergleichenden Dateneigenschaft (''Schlüssel''), eignen sich Bäume also insbesondere, um gesuchte Daten schnell wieder auffinden zu können.&lt;br /&gt;
&lt;br /&gt;
Ein ''Binärbaum'' wie oben skizziert besteht aus einer Menge von ''Knoten'', die untereinander durch ''Kanten'' verbunden sind. Jeder Knoten hat einen linken und einen rechten Unterbaum, der auch leer sein kann (in Python ließe sich dies mit ''None'' implementieren). Führt eine Kante von Knoten A zu Knoten B, so heißt A Vater von B und B Kind von A. Es gibt genau einen Knoten ohne Vater, den man ''Wurzel'' nennt. Knoten ohne Kinder heißen ''Blätter''.&lt;br /&gt;
&lt;br /&gt;
Ein ''Suchbaum'' hat zusätzlich die Eigenschaft, dass die Schlüssel jedes Knotens sortiert sind. Alle Schlüssel im linken Unterbaum sind kleiner, alle Schlüssel im rechten Unterbaum sind größer als ihr Vater. Wir wollen hierbei annehmen, dass jeder Schlüssel pro Datensatz nur einmal vorkommt, da sich sonst die &amp;gt;- oder &amp;lt;-Relation nicht mehr strikt erfüllen ließe.&lt;br /&gt;
&lt;br /&gt;
Um in einem Baum suchen zu können, wollen wir von zwei Annahmen ausgehen:&lt;br /&gt;
# Einfügen und Suchen im Baum wechseln sich ab. (Wenn das Suchen erst beginnt, nachdem alle Einfügungen erfolgt sind, wäre ein dynamisches Array mit binärer Suche wie oben wesentlich einfacher.)&lt;br /&gt;
# Der Schlüssel, der die Anordnung bestimmt, kennt eine [http://de.wikipedia.org/wiki/Ordnungsrelation Ordnung] (&amp;lt;-Relation oder &amp;gt;-Relation).&lt;br /&gt;
&lt;br /&gt;
Der folgende Python-Code zeigt beispielhaft, wie man in einem Suchbaum suchen könnte. Der Konstruktor für einen Knoten des Suchbaums ließe sich zum Beispiel so implementieren:&lt;br /&gt;
 &lt;br /&gt;
 class Node:&lt;br /&gt;
     def __init__(self, key):&lt;br /&gt;
         self.key = key&lt;br /&gt;
         self.left = self.right = None&lt;br /&gt;
 &lt;br /&gt;
 root = ...    # Wurzel des Suchbaums&lt;br /&gt;
 nodeFound = treeSearch(root, key)   # None, falls nichts gefunden&lt;br /&gt;
 &lt;br /&gt;
 def treeSearch(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return None&lt;br /&gt;
     elif node.key == key:&lt;br /&gt;
         return node&lt;br /&gt;
     elif key &amp;lt; node.key:&lt;br /&gt;
         return treeSearch(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         return treeSearch(node.right, key)&lt;br /&gt;
&lt;br /&gt;
Daraus resultiert der folgende Suchalgorithmus:&lt;br /&gt;
&lt;br /&gt;
 def treeSort(node,array):     # dynamisches Array als 2. Argument&lt;br /&gt;
     if node is None:     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
         return None:&lt;br /&gt;
     treeSort(node.left, array)     # rekursiv&lt;br /&gt;
     array.append(node.key)     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
     treeSort(node.right, array)     # rekursiv&lt;br /&gt;
&lt;br /&gt;
Komplexität:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 f(N)=\mathcal{O}(1)+f(N_\mathrm{left})+f(N_\mathrm{right})=\mathcal{O}(1)+\mathcal{O}(1)+f(N_\mathrm{leftleft})+f(N_\mathrm{leftright})+\mathcal{O}(1)+f(N_\mathrm{rightleft})&lt;br /&gt;
 +f(N_\mathrm{leftright})=N\ast\mathcal{O}(1)=\mathcal{O}(N)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sortier-Pseudocode:&lt;br /&gt;
&lt;br /&gt;
 Sortieren:&lt;br /&gt;
     (Array) a     # unsortiert&lt;br /&gt;
     (tree) t     # zunächst leer&lt;br /&gt;
 (dynamisches Array) r     # später sortiert&lt;br /&gt;
 for e in a:&lt;br /&gt;
     t = treeInsert(t, e)&lt;br /&gt;
 treeSort(t, r)&lt;br /&gt;
&lt;br /&gt;
== Insert ==&lt;br /&gt;
&lt;br /&gt;
Was passiert wenn der key (Schlüssel) schon vorhanden ist?&lt;br /&gt;
* error (exception)&lt;br /&gt;
* nichts einfügen&lt;br /&gt;
* nichts einfügen aber einen boolean return zurückgeben (einfügen=true, false)&lt;br /&gt;
* nochmals eingefügt (z.B. in der Node Klasse)&lt;br /&gt;
&lt;br /&gt;
Wobei die ersten 3 Punkte zur Mengensemantik gehören und der letzte eine Multimenge ist.&lt;br /&gt;
&lt;br /&gt;
Algorithmus im zweiten Fall (nichts einfügen):&lt;br /&gt;
&lt;br /&gt;
 def treeInsert(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return Node(key)     # Alternative Schreibweise: node = Node(key)&lt;br /&gt;
     if node.key == key:      # und dann:&lt;br /&gt;
         return node          # pass&lt;br /&gt;
     elif key &amp;lt; node.key:     # links im Baum&lt;br /&gt;
         node.left = treeInsert(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         node.right = treeInsert(node.right, key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Remove ==&lt;br /&gt;
Fälle:&lt;br /&gt;
# key (bzw. Knoten der key enthält) ist ein Blatt =&amp;gt; einfach löschen&lt;br /&gt;
# node hat &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; linken Unterbaum oder &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; rechten Unterbaum =&amp;gt; durch Unterbaum ersetzen&lt;br /&gt;
# node hat beide Unterbäume:&lt;br /&gt;
#* Suche Vorgänger: max k &amp;lt; key (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; keys); Vorgänger ist immer ein Fall 1 oder Fall 2&lt;br /&gt;
#*:=&amp;gt; ersetze node durch Vorgänger und entferne Vorgänger&lt;br /&gt;
&lt;br /&gt;
 def treePredecessor(node):     # wird nur bei Fall 3 aufgerufen&lt;br /&gt;
     node = node.left&lt;br /&gt;
     while node.right is not None:&lt;br /&gt;
         node = node.right&lt;br /&gt;
     return node&lt;br /&gt;
 &lt;br /&gt;
 def treeRemove(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return node&lt;br /&gt;
     if key &amp;lt; node.key:&lt;br /&gt;
         node.left = treeRemove(node.left, key)&lt;br /&gt;
     elif key &amp;gt; node.key:&lt;br /&gt;
         node.right = treeRemove(node.right, key)&lt;br /&gt;
     else:&lt;br /&gt;
         if node.left is None and node.right is None:     # Fall 1&lt;br /&gt;
             node = None&lt;br /&gt;
         elif node.left is None:     # Fall 2&lt;br /&gt;
             node = node.right       # +&lt;br /&gt;
         elif node.right is None:    # Fall 2&lt;br /&gt;
             node = node.left&lt;br /&gt;
         else:&lt;br /&gt;
             pred = treePredecessor(node)&lt;br /&gt;
             node.key = pred.key&lt;br /&gt;
             node.left = treeRemove(node.left, pred.key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Komplexitätsanalyse ==&lt;br /&gt;
&lt;br /&gt;
* Pfad (Zwischen node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;): &lt;br /&gt;
**Folge von Knoten (node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt;,...,node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt;), sodass:&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;ki&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;ki+1&amp;lt;/sub&amp;gt; haben eine gemeinsame Kante.&lt;br /&gt;
[[Image:Baum_Pfad.png]]&lt;br /&gt;
(Ein Baum ist ein Graph, indem es zwischen beliebigen Knoten stets genau einen Pfad gibt.)&lt;br /&gt;
&lt;br /&gt;
* Länge eines Pfades: Anzahl der Kanten = Anzahl der Knoten - 1&lt;br /&gt;
* Tiefe eines Knotens: Pfadlänge vom Knoten zur Wurzel des Baumes.&lt;br /&gt;
* Tiefe des Baumes: maximale Tiefe eines Knotens&lt;br /&gt;
* Balance eines Baumes: maximale Tiefe(k) - minimale Tiefe(k) (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; Blätter)&lt;br /&gt;
&lt;br /&gt;
== Ungünstigster Fall von treeSearch ==&lt;br /&gt;
&lt;br /&gt;
Komplexität von treeSearch = Länge des Pfades zum Knoten wo &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; gefunden wird, oder erkannt wird, dass &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; nicht im Baum ist.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; wird nicht gefunden, aber für diese Entscheidung muss der Längste Pfad vollständig durchlaufen werden.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(T)&amp;lt;/math&amp;gt; wo T = Tiefe des Baumes&lt;br /&gt;
*: [1,2,3,4,5]:&amp;lt;br /&amp;gt; [[Image:Balance.png]]&lt;br /&gt;
*: Tiefe T = 4, Balance = 4&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; wo N = Anzahl der Elemente.&lt;br /&gt;
&lt;br /&gt;
== Aufgabe ==&lt;br /&gt;
&lt;br /&gt;
Minimiere Balance (erzeuge balancierten Baum):&lt;br /&gt;
# Einfügen in geschickter Reihenfolge (siehe Übungsaufgabe)&lt;br /&gt;
# Selbstbalancierter Baum:&lt;br /&gt;
#* Überprüfen der Balance nach jedem Einfügen&lt;br /&gt;
#* Umstrukturieren des Baumes, falls Balance &amp;gt; 1 (Suchbaum-Bedingung muss erhalten bleiben)&lt;br /&gt;
#* AVL-Bäume (älteste Variante)&lt;br /&gt;
#* Rot-Schwarz-Bäume (verbreiteste Variante)&lt;br /&gt;
#* Treaps (flexibelste Variante, siehe Übung)&lt;br /&gt;
#* Splay trees&lt;br /&gt;
#* Andersson Trees (einfachste Variante)&lt;br /&gt;
(#* Skip Lists (schnellste Variante, aber kein Binärbaum))&lt;br /&gt;
&lt;br /&gt;
== Umstrukturieren, so dass Suchbaumbedingung erhalten bleibt: ==&lt;br /&gt;
&lt;br /&gt;
Rotation: elementare Umstrukturierungen&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum_Rotation.png]]&lt;br /&gt;
&lt;br /&gt;
 def rotateRight(node):&lt;br /&gt;
     newRoot = node.left&lt;br /&gt;
     node.left = newRoot.right&lt;br /&gt;
     newRoot.right = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
 def rotateLeft(node):&lt;br /&gt;
     newRoot = node.right&lt;br /&gt;
     node.right = newRoot.left&lt;br /&gt;
     newRoot.left = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
== Balance eines Suchbaumes ==&lt;br /&gt;
&lt;br /&gt;
===Balance eines Baumes zu  definieren:===&lt;br /&gt;
[[Image:Abbildung1.jpg|250px|right]]&lt;br /&gt;
*betrachte &amp;lt;tt&amp;gt;None&amp;lt;/tt&amp;gt; als zusätzlichen Knoten : '''sentinel'''   ([[englische Sprache|engl.]] für ''Wächter'')&lt;br /&gt;
*oder defeniere speziellen sentinel-Knoten&lt;br /&gt;
*RS - Pfades : von &amp;lt;tt&amp;gt;root&amp;lt;/tt&amp;gt; &amp;amp;rarr; ''sentinel''&lt;br /&gt;
*Länge eines Pfades ''P''      '''''|P|'''''&lt;br /&gt;
* &amp;lt;u&amp;gt;die Balance eines Baumes zu defenieren:&amp;lt;/u&amp;gt;&lt;br /&gt;
: '''''B = max P '''''&amp;lt;math&amp;gt; \in &amp;lt;/math&amp;gt; '''''{RS}    |P| - min |P|'''''&lt;br /&gt;
:'''''{RS}'''''  Menge aller RS-Pfade&lt;br /&gt;
;vollständiger Baum:  Balance &amp;lt;math&amp;gt;~=0&amp;lt;/math&amp;gt;&lt;br /&gt;
:Alle Knoten(außer Blättern) haben 2 Kinder.&lt;br /&gt;
;perfect balancierter Baum:  Balance  &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 1&lt;br /&gt;
'''alternative Defenition für perfect balancierte Bäume'''&lt;br /&gt;
:Für jeden Teilbaum gilt es: rechtes und linkes Kind (für jeden Knoten) sind auch wieder perfect balancierte Bäume und ihre Höhe unterscheidet sich höchstens um '''1'''.&lt;br /&gt;
&lt;br /&gt;
===Größe===&lt;br /&gt;
[[Image:baum-v.png|700px|right]]&lt;br /&gt;
====Größe des vollständigen Baumes====&lt;br /&gt;
Ebene K hat 2 &amp;lt;sup&amp;gt;k&amp;lt;/sup&amp;gt; Knoten.&lt;br /&gt;
Falls Tiefe = d , dann N = 2&amp;lt;sup&amp;gt;0&amp;lt;/sup&amp;gt; + 2&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;.....+ 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; = 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1&lt;br /&gt;
&lt;br /&gt;
====Größe des perfect balancierten Baumes====&lt;br /&gt;
Die Tiefe d, kann nicht besser sein als vollständiger Baum&lt;br /&gt;
:'''=&amp;gt;'''N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2 &amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
;schlechteste perfect balancierter Baum : ist ein vollständiger der Tiefe  d - 1 + 1, N &amp;lt;math&amp;gt; \ge&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:'''=&amp;gt;'''  2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1)   &amp;lt;math&amp;gt;~&amp;lt;&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt;)&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
d &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt;  log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; (d + 1)&lt;br /&gt;
&lt;br /&gt;
*Ergibt die Komplexität der Suche im schlechtesten Fall: Anzahl der Vergleiche pro Knoten( = 2 bzw. = 3)&amp;lt;math&amp;gt;\ast&amp;lt;/math&amp;gt;Anzahl der Knoten&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow ~f(N)\le 2d \le 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
*'''perfekt balancierter Baum '''=&amp;gt; AVL-Baum &lt;br /&gt;
*'''balancierter Baum''':&lt;br /&gt;
::&amp;lt;math&amp;gt;\forall  N:d(N)\le c \ast d (N)&amp;lt;/math&amp;gt; und  &amp;lt;math&amp;gt;1 \le c &amp;lt; \mathcal {1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:: d ist die Tiefe von perfekt balancierten Baum&lt;br /&gt;
&lt;br /&gt;
:''Komplexität der Suche'':&amp;lt;math&amp;gt; ~f(N)\le  c\ast 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
:algorithmisch einfacher als perfekt balancierter Baum, aber fast genauso schnell&lt;br /&gt;
&lt;br /&gt;
====Selbst-balancierter Baum====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  def insertTree(node,key):&lt;br /&gt;
    if node is sentinel:       #(None = sentinel)&lt;br /&gt;
        return Node(key)       &lt;br /&gt;
    if node.key == key:&lt;br /&gt;
        return Node&lt;br /&gt;
    if key &amp;lt; node.key:&lt;br /&gt;
        Node = insertTree(node.left, key)&lt;br /&gt;
    else:&lt;br /&gt;
        node.right = insertTree(node.right, key)&lt;br /&gt;
   #optimiere Balance:&lt;br /&gt;
        return node&lt;br /&gt;
&lt;br /&gt;
===Anderson-Bäume===&lt;br /&gt;
&lt;br /&gt;
  class Node:&lt;br /&gt;
    def__init__(self, key):&lt;br /&gt;
        self.key = key&lt;br /&gt;
        self.left = selft.right = None&lt;br /&gt;
        #einfügen:&lt;br /&gt;
        self.level = 1&lt;br /&gt;
&lt;br /&gt;
:''level'' : kodiert Abstand von sentinel&lt;br /&gt;
====Regeln====&lt;br /&gt;
Es gibt vertikale Kanten(parent.level == child.level + 1 ) und horizontale Kanten(parent.level == child.level)&lt;br /&gt;
:*verfeinerter Regel :  ''level kodiert reduzierten Abstand von Setinel''  (d.b. horizontale Kanten werden nicht gezählt)&lt;br /&gt;
::(die nächste zwei Regeln sichern die Balance):&lt;br /&gt;
::*alle RS-Pfade haben die gleiche reduzierte Länge oder ''root'' hat bei allen Pfaden das gleiche Level&lt;br /&gt;
::*kein Pfad hat 2 aufeinanderer folgende horizontale Kanten&lt;br /&gt;
:*nur Kanten zum rechten Kind dürfen horizontal sein&lt;br /&gt;
:*die reduzierte Höhe jedes Blatts ist '''hr=1'''&lt;br /&gt;
&lt;br /&gt;
====Beweis====&lt;br /&gt;
Vereinfachung des Algorithmus&lt;br /&gt;
:'''Satz''':ein Anderson-Baum ist balanciert.&lt;br /&gt;
*1. Sei '''hr'''- die reduzierte Höhe&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; jeder Teilbaum enthält mindestens &amp;lt;math&amp;gt;N\ge 2^{hr} -1 &amp;lt;/math&amp;gt;  Knoten&lt;br /&gt;
&lt;br /&gt;
::a). Blätter : reduzierte Höhe 1 =&amp;gt; &amp;lt;math&amp;gt; N\ge 2^{1} - 1 = 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
::b). inneren Knoten: jeder Unterbaum hat mindestens reduzierte Höhe &amp;lt;math&amp;gt; ~hr - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt;  jeder Unterbaum hat mindestens &amp;lt;math&amp;gt; ~2^{hr} -1 &amp;lt;/math&amp;gt; Knoten&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt;N \ge 2 (2^{hr-1} -1)+1 = 2^w - 2 + 1 = 2^{hr} -1&amp;lt;/math&amp;gt;&lt;br /&gt;
::::alle RS-Pfade heben gleiche Länge&lt;br /&gt;
[[Image:Anderson.png|700px|right]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
*2. Kein Pfad hat 2 aufeinanderfolgende horizontal Kanten&lt;br /&gt;
:::&amp;lt;math&amp;gt;\Rightarrow  d \le 2 hr + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
*3. zusammen:&lt;br /&gt;
:&amp;lt;math&amp;gt;N \ge 2^{\frac {d}{2}} - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;log_{2}{ N}+1\ge log 2^{\frac {d}{2}} - 1 \ge \frac {d}{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; ~d &amp;lt; 2 log_{2}{(N + 1)}&amp;lt;/math&amp;gt;     - balancierter Baum&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
:'''Suchzeit''' : &amp;lt;math&amp;gt; ~ f(N) = \mathcal{O}(log{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Wie erreicht man die Balance===&lt;br /&gt;
====Rotation====&lt;br /&gt;
[[Image:Abbildung4.jpg|350px|Illustration von Rotation]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
   def rotateRight (node):&lt;br /&gt;
      root = node.left&lt;br /&gt;
      node.left = node.right&lt;br /&gt;
      root.right = node&lt;br /&gt;
      return root&lt;br /&gt;
   def rotateLeft(node):&lt;br /&gt;
      root = node.right&lt;br /&gt;
      node.right = node.left&lt;br /&gt;
      root.left = node&lt;br /&gt;
      return root&lt;br /&gt;
&lt;br /&gt;
====Optimierung der Balance====&lt;br /&gt;
   &lt;br /&gt;
&lt;br /&gt;
       if node.left is not sentinel and node.level==node.left.level:&lt;br /&gt;
            node &amp;gt; rotateRight(node)&lt;br /&gt;
       if node.right is not sentinel and node.right.right is  not sentinel and node.level==rotate.right.right.level:&lt;br /&gt;
            node = rotateLeft(node)&lt;br /&gt;
            node.level += 1&lt;/div&gt;</summary>
		<author><name>Nata</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1489</id>
		<title>Suchen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1489"/>
				<updated>2008-05-29T00:15:04Z</updated>
		
		<summary type="html">&lt;p&gt;Nata: /* Rotation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Es wäre super wenn jemand ganz kurz schreiben könnte, was am Donnerstag zu den Funktionen treeInsert/Remove/HasKey gemacht wurde, was man ja für den aktuellen Übungszettel braucht. Danke :-)&amp;lt;br /&amp;gt;&lt;br /&gt;
/edit: an eine Funktion &amp;quot;HasKey&amp;quot; kann ich mich aus der Vorlesung nicht erinnern. Wenn die jemand hat, bitte eintragen. - Protokollant&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--Hallo, ich kenne leider deinen Namen nicht, ich habe den Wiki-Eintrag von letztem Donnerstag gemacht.Da das Thema 'Dynamisches Array' noch Thema von letzter Woche war, hab ich die Wiederholung der amortisierten Kosten von diesem Mittwoch noch bei Effizienz eingetragen, du musst es also nicht noch mal in deinem Wiki eintragen. Du kannst dir aber gerne mal anschauen was ich geschrieben habe, vielleicht fallen dir noch ein paar Sachen ein, die man hätte besser machen können. lg, Franziska.--&amp;gt;&lt;br /&gt;
Das Suchen ist eine grundlegende Operation in der Informatik. Viele Probleme in der Informatik können auf Suchaufgaben zurückgeführt werden.&lt;br /&gt;
&lt;br /&gt;
Gemeint ist mit Suchen das Wiederauffinden einer oder mehrerer Datensätze aus einer Menge von früher gespeicherten Datensätzen. Ein paar einleitende Worte zum Suchproblem findet man [http://de.wikipedia.org/wiki/Suche hier].&lt;br /&gt;
&lt;br /&gt;
== Überblick verschiedener Suchmethoden ==&lt;br /&gt;
&lt;br /&gt;
Um sich der Vielseitigkeit von Suchproblemen bewusst zu werden, ist es sinnvoll, sich einen Überblick über verschiedene Suchmethoden zu verschaffen. &lt;br /&gt;
&lt;br /&gt;
Hier sei auch auf einen bereits existierenden Wikipedia-Artikel zu [http://de.wikipedia.org/wiki/Suchverfahren Suchverfahren] verwiesen.&lt;br /&gt;
&lt;br /&gt;
Allen gemeinsam ist die grundlegende Aufgabe, ein Datenelement mit bestimmten Eigenschaften aus einer großen Menge von Datenelementen zu selektieren.&lt;br /&gt;
Dies kann, natürlich ohne jeden Anspruch auf Vollständigkeit, nach einer der jetzt diskutierten Methoden geschehen:&lt;br /&gt;
&lt;br /&gt;
* '''Schlüsselsuche''': meint das Suchen von Elementen mit bestimmtem Schlüssel; ein klassisches Beispiel wäre das Suchen in einem Wörterbuch,  die Schlüssel entsprechen hier den Wörtern, die Datensätze wären die zu den Wörtern gehörigen Eintragungen.&lt;br /&gt;
&lt;br /&gt;
* '''Bereichssuche''': Im Allgemeinen meint die Bereichssuche in n-Dimensionen die Selektion von Elementen mit Eigenschaften aus einem bestimmten n-dimensionalen Volumen. Im eindimensionalen Fall will man alle Elemente finden, deren Eigenschaft(en) in einem bestimmten Intervall liegen. Die Verallgemeinerung auf n-Dimensionen ist offensichtlich. Ein Beispiel für die Bereichssuche in einer 3D-Kugel wäre ein Handy mit Geolokalisierung, welches alle Restaurants in einem Umkreis von 500m findet. Lineare Ungleichungen werden graphisch durch [http://de.wikipedia.org/wiki/Hyperebene Hyperebenen] repräsentiert. In 2D sind diese Hyperebenen Geraden. Die Ungleichungen können dann den Lösungsraum in irgendeiner Form begrenzen.&lt;br /&gt;
&lt;br /&gt;
* '''Ähnlichkeitssuche''': Finde Elemente, die gegebenen Eigenschaften möglichst ähnlich sind. Ein prominentes Beispiel ist Google (=Ähnlichkeit zwischen Suchbegriffen und Dokumenten) oder das Suchen des nächstengelegenen Restaurants (Ähnlichkeit zwischen eigener Position und Position des Restaurants). Ein wichtigster Spezialfall ist die ''nächste-nachbar Suche''.&lt;br /&gt;
&lt;br /&gt;
* '''Graphensuche''': Hier wäre beispielsweise das Problem optimaler Wege zu nennen (Navigationssuche). Dieser Punkt wird später im Verlauf der Vorlesung noch einmal aufgegriffen werden.&lt;br /&gt;
&lt;br /&gt;
Im jetzt Folgenden wird nur noch die ''Schlüsselsuche'' betrachtet werden.&lt;br /&gt;
&lt;br /&gt;
==Sequentielle Suche==&lt;br /&gt;
&lt;br /&gt;
Die ''sequentielle'' oder ''lineare'' Suche ist die 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 = sequentialSearch(a, key) &lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq &amp;lt;/math&amp;gt; foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def sequentialSearch(a, key):&lt;br /&gt;
    for i in range(len(a)):&lt;br /&gt;
        if a[i] == key:  # bzw. allgemeiner a[i].key == key &lt;br /&gt;
            return i&lt;br /&gt;
    return -1&lt;br /&gt;
&lt;br /&gt;
Wir wollen jetzt die Komplexität dieses Algorithmus bestimmen, wobei die Problemgröße durch &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt; gegeben ist. &lt;br /&gt;
&lt;br /&gt;
Dabei nimmt man an, dass der innerste Vergleich (a[i] == key) jeweils &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt; ist (diese Annahme könnte verletzt sein, wenn der Vergleichsoperator überladen ist und dadurch eine höhere Komplexität hat). Dieser Vergleich wird in der for-Schleife jeweils N-mal durchgeführt (&amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt;), so dass man nach der Verschachtelungsregel eine gesamte Komplexität von &amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt; erhält.&lt;br /&gt;
&lt;br /&gt;
Der Name ''lineare'' Suche rührt von diesem linearen Anwachsen der Komplexität mit der Arraygröße her.&lt;br /&gt;
&lt;br /&gt;
==Binäre Suche==&lt;br /&gt;
&lt;br /&gt;
Wie wir weiter unten zeigen werden, gestattet es diese Suchmethode, die Gesamtdauer der Suche in großen Datensätzen beträchtlich zu verringern. Die Methode beruht auf dem [http://de.wikipedia.org/wiki/Divide_and_Conquer Divide and Conquer-Prinzip], wobei die Suche in jedem Schritt rekursiv auf eine Hälfte des Datensatzes eingeschränkt wird. Weitere Details zur Methode sind [http://de.wikipedia.org/wiki/Bin%C3%A4re_Suche hier] zu finden. &lt;br /&gt;
&lt;br /&gt;
Die Methode ist nur dann anwendbar beziehungsweise effektiv, wenn folgendes gilt:&lt;br /&gt;
&lt;br /&gt;
# Auf der Eigenschaft der Daten, die zur Suche verwendet wird, ist eine Ordnung im Sinne von &amp;lt; oder &amp;gt; definiert.&lt;br /&gt;
# Wir wollen uns auf Datensätze beschränken, die schon fertig aufgebaut sind, in die also keine neuen Elemente mehr eingefügt werden, wenn man mit dem Suchen beginnt. Ist dies nicht der Fall, so ist unter Umständen die Implementierung über einen [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum Binärbaum] (siehe auch weiter unten) geschickter. &lt;br /&gt;
&lt;br /&gt;
Der folgende Algorithmus zeigt eine beispielhafte Implementierung der Methode (wir setzen wieder &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt;):&lt;br /&gt;
&lt;br /&gt;
 a = [...,...]     # array&lt;br /&gt;
 a.sort()   # sortiere über Ordnung des Schlüssels&lt;br /&gt;
 foundIndex = binSearch(a, key, 0, len(a))  # (Array, Schlüssel, von wo bis wo suchen im Array)&lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq&amp;lt;/math&amp;gt;  foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def binSearch(a, key, start, end):  # start ist 1. Index, end ist letzter Index + 1&lt;br /&gt;
    size = end - start   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if size &amp;lt;= 0:   # Bereich leer?  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return -1   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    center = (start + end)/2   # Integer Division, Ergebnis wird abgerundet, wichtig für ganzzahlige Indizes &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if a[center] == key:  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return center  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    elif a[center] &amp;lt; key:  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return binSearch(a, key, center + 1, end)  # Rekursion&lt;br /&gt;
    else:&lt;br /&gt;
        return binSearch(a, key, start + 1, center)  # Rekursion&lt;br /&gt;
&lt;br /&gt;
Zur Berechnung der Komplexität dieses Algorithmus vernachlässigen wir zunächst den Aufwand, den die Sortierung weiter oben verursacht. Dieser Schritt mag oder mag nicht zulässig sein.&lt;br /&gt;
&lt;br /&gt;
Nach der Sequenzregel haben auch alle &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;.  Es bleibt die Komplexität der Rekursion zu berechnen. Die gesamte Komplexität des Algorithmus (jetzt als Funktion f bezeichnet) setzt sich zusammen aus den oben erwähnten &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen sowie der Rekursion und ist &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f(N) = \mathcal{O}(1) + f(N/2) = \mathcal{O}(1) + \mathcal{O}(1) + f(N/4) = ... = \underbrace{\mathcal{O}(1) + ... + \mathcal{O}(1) + \underbrace{f(0)}_{\mathcal{O}(1)\, \rightarrow \,\mathrm{size-Abfrage}}}_{n+1 \,\mathrm{Terme}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Falls jetzt gilt &amp;lt;math&amp;gt; N = 2^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \rightarrow f(N) = \mathcal{O}(1) \cdot \mathcal{O}(n+1) = \mathcal{O}(n) = \mathcal{O}(\lg N) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Für große Datenmengen ist die ''binäre Suche'' also weit effizienter als die ''lineare Suche''. Verdoppelt sich beispielsweise die zu durchsuchende Datenmenge, so verdoppelt sich der Aufwand für die ''sequentielle Suche'' - bei der ''binären Suche'' hingegen benötigt man lediglich eine zusätzliche Vergleichsoperation. &lt;br /&gt;
&lt;br /&gt;
Für kleine Daten (&amp;lt;math&amp;gt; N = 4,\, 5 &amp;lt;/math&amp;gt;) ist die ''sequentielle Suche'' jedoch schneller als die ''binäre Suche'', da hier die rekursiven Funktionsaufrufe teurer als das Mehr an Vergleichen sind. Ein anderer ungünstiger Fall ist gegeben, wenn nur sehr wenige Suchanfragen erfolgen (weniger als &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; viele). Dann wird der Aufwand durch das Sortieren des Arrays dominiert, ist also &amp;lt;math&amp;gt;\mathcal{O}(N \lg N) &amp;lt;/math&amp;gt;. Auch dann ist sequentielle Suche vorzuziehen.&lt;br /&gt;
&lt;br /&gt;
Eine relativ einfache Möglichkeit, die ''binäre Suche'' zu verbessern, ist die sogenannte ''Interpolationssuche''. Hierbei wird die neue Position für die Suche, also die Mitte des Arrays, durch eine Schätzung ersetzt, die angibt, wo sich der Schlüssel innerhalb des Arrays befinden könnte. Bei der Suche in einem Telefonbuch nach dem Namen Zebra würde man ja auch nicht in der Mitte anfangen. Näheres hierzu im Buch von ''Sedgewick''.&lt;br /&gt;
&lt;br /&gt;
Um sich den Algorithmus der ''binären Suche'' klar zu machen, ist es instruktiv, sich die folgende Tabelle genauer anzusehen, die die sukzessive Belegung der Variablen bei verschiedenen Anfragen beschreibt. Die Testfälle wurden nach dem Prinzip des ''domain partitioning'' gewählt. Das zugehörige Array hat die Einträge&lt;br /&gt;
&lt;br /&gt;
 a = [2, 3, 4, 5, 6]&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align:center&amp;quot; border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;5&amp;quot; cellspacing=&amp;quot;0&amp;quot; &lt;br /&gt;
! gesuchter key   !!  start      !! end  !! size !! center !! return &amp;lt;br/&amp;gt; (-1 oder index)  !! Kommentare  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 4     ||0            || 5    ||  5   || 2   ||  2         || gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 2     || 0           || 5    ||  5   || 2   ||            || linker Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||           &lt;br /&gt;
|-&lt;br /&gt;
|       ||  0          || 1    ||  1   || 0   ||  0         || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 1     ||0            || 5    ||  5   || 2   ||            || links außerhalb&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 1    ||  1   || 0   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 0    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 6     ||0            || 5    ||  5   || 2   ||            || rechter Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    || 2    || 4   || 4          || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 5     ||0            || 5    ||  5   || 2   ||            || typischer Fall&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||3            || 5    ||  2   || 4   ||            ||  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       || 3           || 4    ||  1   || 3   || 3          || gefunden&lt;br /&gt;
|- &lt;br /&gt;
| 7     ||0            || 5    ||  5   || 2   ||            || rechts außerhalb        &lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    ||  2   || 4   ||            ||&lt;br /&gt;
|-&lt;br /&gt;
|       ||5            || 5    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Suche in einem Binärbaum ==&lt;br /&gt;
&lt;br /&gt;
Eine kurze Einführung in Binärbäume findet man [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum hier].&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum.png|text-top|300x300px|Zur Illustration von Bäumen]]&lt;br /&gt;
&lt;br /&gt;
Bäume sind zweidimensional verkettete Strukturen. Sie gehören zu den fundamentalen Datenstrukturen in der Informatik. Da man in Bäumen nicht nur Daten speichern kann, sondern auch relevante Beziehungen der Daten untereinander, festgelegt über eine Ordnung auf der vergleichenden Dateneigenschaft (''Schlüssel''), eignen sich Bäume also insbesondere, um gesuchte Daten schnell wieder auffinden zu können.&lt;br /&gt;
&lt;br /&gt;
Ein ''Binärbaum'' wie oben skizziert besteht aus einer Menge von ''Knoten'', die untereinander durch ''Kanten'' verbunden sind. Jeder Knoten hat einen linken und einen rechten Unterbaum, der auch leer sein kann (in Python ließe sich dies mit ''None'' implementieren). Führt eine Kante von Knoten A zu Knoten B, so heißt A Vater von B und B Kind von A. Es gibt genau einen Knoten ohne Vater, den man ''Wurzel'' nennt. Knoten ohne Kinder heißen ''Blätter''.&lt;br /&gt;
&lt;br /&gt;
Ein ''Suchbaum'' hat zusätzlich die Eigenschaft, dass die Schlüssel jedes Knotens sortiert sind. Alle Schlüssel im linken Unterbaum sind kleiner, alle Schlüssel im rechten Unterbaum sind größer als ihr Vater. Wir wollen hierbei annehmen, dass jeder Schlüssel pro Datensatz nur einmal vorkommt, da sich sonst die &amp;gt;- oder &amp;lt;-Relation nicht mehr strikt erfüllen ließe.&lt;br /&gt;
&lt;br /&gt;
Um in einem Baum suchen zu können, wollen wir von zwei Annahmen ausgehen:&lt;br /&gt;
# Einfügen und Suchen im Baum wechseln sich ab. (Wenn das Suchen erst beginnt, nachdem alle Einfügungen erfolgt sind, wäre ein dynamisches Array mit binärer Suche wie oben wesentlich einfacher.)&lt;br /&gt;
# Der Schlüssel, der die Anordnung bestimmt, kennt eine [http://de.wikipedia.org/wiki/Ordnungsrelation Ordnung] (&amp;lt;-Relation oder &amp;gt;-Relation).&lt;br /&gt;
&lt;br /&gt;
Der folgende Python-Code zeigt beispielhaft, wie man in einem Suchbaum suchen könnte. Der Konstruktor für einen Knoten des Suchbaums ließe sich zum Beispiel so implementieren:&lt;br /&gt;
 &lt;br /&gt;
 class Node:&lt;br /&gt;
     def __init__(self, key):&lt;br /&gt;
         self.key = key&lt;br /&gt;
         self.left = self.right = None&lt;br /&gt;
 &lt;br /&gt;
 root = ...    # Wurzel des Suchbaums&lt;br /&gt;
 nodeFound = treeSearch(root, key)   # None, falls nichts gefunden&lt;br /&gt;
 &lt;br /&gt;
 def treeSearch(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return None&lt;br /&gt;
     elif node.key == key:&lt;br /&gt;
         return node&lt;br /&gt;
     elif key &amp;lt; node.key:&lt;br /&gt;
         return treeSearch(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         return treeSearch(node.right, key)&lt;br /&gt;
&lt;br /&gt;
Daraus resultiert der folgende Suchalgorithmus:&lt;br /&gt;
&lt;br /&gt;
 def treeSort(node,array):     # dynamisches Array als 2. Argument&lt;br /&gt;
     if node is None:     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
         return None:&lt;br /&gt;
     treeSort(node.left, array)     # rekursiv&lt;br /&gt;
     array.append(node.key)     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
     treeSort(node.right, array)     # rekursiv&lt;br /&gt;
&lt;br /&gt;
Komplexität:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 f(N)=\mathcal{O}(1)+f(N_\mathrm{left})+f(N_\mathrm{right})=\mathcal{O}(1)+\mathcal{O}(1)+f(N_\mathrm{leftleft})+f(N_\mathrm{leftright})+\mathcal{O}(1)+f(N_\mathrm{rightleft})&lt;br /&gt;
 +f(N_\mathrm{leftright})=N\ast\mathcal{O}(1)=\mathcal{O}(N)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sortier-Pseudocode:&lt;br /&gt;
&lt;br /&gt;
 Sortieren:&lt;br /&gt;
     (Array) a     # unsortiert&lt;br /&gt;
     (tree) t     # zunächst leer&lt;br /&gt;
 (dynamisches Array) r     # später sortiert&lt;br /&gt;
 for e in a:&lt;br /&gt;
     t = treeInsert(t, e)&lt;br /&gt;
 treeSort(t, r)&lt;br /&gt;
&lt;br /&gt;
== Insert ==&lt;br /&gt;
&lt;br /&gt;
Was passiert wenn der key (Schlüssel) schon vorhanden ist?&lt;br /&gt;
* error (exception)&lt;br /&gt;
* nichts einfügen&lt;br /&gt;
* nichts einfügen aber einen boolean return zurückgeben (einfügen=true, false)&lt;br /&gt;
* nochmals eingefügt (z.B. in der Node Klasse)&lt;br /&gt;
&lt;br /&gt;
Wobei die ersten 3 Punkte zur Mengensemantik gehören und der letzte eine Multimenge ist.&lt;br /&gt;
&lt;br /&gt;
Algorithmus im zweiten Fall (nichts einfügen):&lt;br /&gt;
&lt;br /&gt;
 def treeInsert(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return Node(key)     # Alternative Schreibweise: node = Node(key)&lt;br /&gt;
     if node.key == key:      # und dann:&lt;br /&gt;
         return node          # pass&lt;br /&gt;
     elif key &amp;lt; node.key:     # links im Baum&lt;br /&gt;
         node.left = treeInsert(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         node.right = treeInsert(node.right, key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Remove ==&lt;br /&gt;
Fälle:&lt;br /&gt;
# key (bzw. Knoten der key enthält) ist ein Blatt =&amp;gt; einfach löschen&lt;br /&gt;
# node hat &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; linken Unterbaum oder &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; rechten Unterbaum =&amp;gt; durch Unterbaum ersetzen&lt;br /&gt;
# node hat beide Unterbäume:&lt;br /&gt;
#* Suche Vorgänger: max k &amp;lt; key (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; keys); Vorgänger ist immer ein Fall 1 oder Fall 2&lt;br /&gt;
#*:=&amp;gt; ersetze node durch Vorgänger und entferne Vorgänger&lt;br /&gt;
&lt;br /&gt;
 def treePredecessor(node):     # wird nur bei Fall 3 aufgerufen&lt;br /&gt;
     node = node.left&lt;br /&gt;
     while node.right is not None:&lt;br /&gt;
         node = node.right&lt;br /&gt;
     return node&lt;br /&gt;
 &lt;br /&gt;
 def treeRemove(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return node&lt;br /&gt;
     if key &amp;lt; node.key:&lt;br /&gt;
         node.left = treeRemove(node.left, key)&lt;br /&gt;
     elif key &amp;gt; node.key:&lt;br /&gt;
         node.right = treeRemove(node.right, key)&lt;br /&gt;
     else:&lt;br /&gt;
         if node.left is None and node.right is None:     # Fall 1&lt;br /&gt;
             node = None&lt;br /&gt;
         elif node.left is None:     # Fall 2&lt;br /&gt;
             node = node.right       # +&lt;br /&gt;
         elif node.right is None:    # Fall 2&lt;br /&gt;
             node = node.left&lt;br /&gt;
         else:&lt;br /&gt;
             pred = treePredecessor(node)&lt;br /&gt;
             node.key = pred.key&lt;br /&gt;
             node.left = treeRemove(node.left, pred.key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Komplexitätsanalyse ==&lt;br /&gt;
&lt;br /&gt;
* Pfad (Zwischen node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;): &lt;br /&gt;
**Folge von Knoten (node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt;,...,node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt;), sodass:&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;ki&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;ki+1&amp;lt;/sub&amp;gt; haben eine gemeinsame Kante.&lt;br /&gt;
[[Image:Baum_Pfad.png]]&lt;br /&gt;
(Ein Baum ist ein Graph, indem es zwischen beliebigen Knoten stets genau einen Pfad gibt.)&lt;br /&gt;
&lt;br /&gt;
* Länge eines Pfades: Anzahl der Kanten = Anzahl der Knoten - 1&lt;br /&gt;
* Tiefe eines Knotens: Pfadlänge vom Knoten zur Wurzel des Baumes.&lt;br /&gt;
* Tiefe des Baumes: maximale Tiefe eines Knotens&lt;br /&gt;
* Balance eines Baumes: maximale Tiefe(k) - minimale Tiefe(k) (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; Blätter)&lt;br /&gt;
&lt;br /&gt;
== Ungünstigster Fall von treeSearch ==&lt;br /&gt;
&lt;br /&gt;
Komplexität von treeSearch = Länge des Pfades zum Knoten wo &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; gefunden wird, oder erkannt wird, dass &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; nicht im Baum ist.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; wird nicht gefunden, aber für diese Entscheidung muss der Längste Pfad vollständig durchlaufen werden.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(T)&amp;lt;/math&amp;gt; wo T = Tiefe des Baumes&lt;br /&gt;
*: [1,2,3,4,5]:&amp;lt;br /&amp;gt; [[Image:Balance.png]]&lt;br /&gt;
*: Tiefe T = 4, Balance = 4&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; wo N = Anzahl der Elemente.&lt;br /&gt;
&lt;br /&gt;
== Aufgabe ==&lt;br /&gt;
&lt;br /&gt;
Minimiere Balance (erzeuge balancierten Baum):&lt;br /&gt;
# Einfügen in geschickter Reihenfolge (siehe Übungsaufgabe)&lt;br /&gt;
# Selbstbalancierter Baum:&lt;br /&gt;
#* Überprüfen der Balance nach jedem Einfügen&lt;br /&gt;
#* Umstrukturieren des Baumes, falls Balance &amp;gt; 1 (Suchbaum-Bedingung muss erhalten bleiben)&lt;br /&gt;
#* AVL-Bäume (älteste Variante)&lt;br /&gt;
#* Rot-Schwarz-Bäume (verbreiteste Variante)&lt;br /&gt;
#* Treaps (flexibelste Variante, siehe Übung)&lt;br /&gt;
#* Splay trees&lt;br /&gt;
#* Andersson Trees (einfachste Variante)&lt;br /&gt;
(#* Skip Lists (schnellste Variante, aber kein Binärbaum))&lt;br /&gt;
&lt;br /&gt;
== Umstrukturieren, so dass Suchbaumbedingung erhalten bleibt: ==&lt;br /&gt;
&lt;br /&gt;
Rotation: elementare Umstrukturierungen&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum_Rotation.png]]&lt;br /&gt;
&lt;br /&gt;
 def rotateRight(node):&lt;br /&gt;
     newRoot = node.left&lt;br /&gt;
     node.left = newRoot.right&lt;br /&gt;
     newRoot.right = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
 def rotateLeft(node):&lt;br /&gt;
     newRoot = node.right&lt;br /&gt;
     node.right = newRoot.left&lt;br /&gt;
     newRoot.left = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
== Balance eines Suchbaumes ==&lt;br /&gt;
&lt;br /&gt;
===Balance eines Baumes zu  definieren:===&lt;br /&gt;
[[Image:Abbildung1.jpg|250px|right]]&lt;br /&gt;
*betrachte &amp;lt;tt&amp;gt;None&amp;lt;/tt&amp;gt; als zusätzlichen Knoten : '''sentinel'''   ([[englische Sprache|engl.]] für ''Wächter'')&lt;br /&gt;
*oder defeniere speziellen sentinel-Knoten&lt;br /&gt;
*RS - Pfades : von &amp;lt;tt&amp;gt;root&amp;lt;/tt&amp;gt; &amp;amp;rarr; ''sentinel''&lt;br /&gt;
*Länge eines Pfades ''P''      '''''|P|'''''&lt;br /&gt;
* &amp;lt;u&amp;gt;die Balance eines Baumes zu defenieren:&amp;lt;/u&amp;gt;&lt;br /&gt;
: '''''B = max P '''''&amp;lt;math&amp;gt; \in &amp;lt;/math&amp;gt; '''''{RS}    |P| - min |P|'''''&lt;br /&gt;
:'''''{RS}'''''  Menge aller RS-Pfade&lt;br /&gt;
;vollständiger Baum:  Balance &amp;lt;math&amp;gt;~=0&amp;lt;/math&amp;gt;&lt;br /&gt;
:Alle Knoten(außer Blättern) haben 2 Kinder.&lt;br /&gt;
;perfect balancierter Baum:  Balance  &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 1&lt;br /&gt;
'''alternative Defenition für perfect balancierte Bäume'''&lt;br /&gt;
:Für jeden Teilbaum gilt es: rechtes und linkes Kind (für jeden Knoten) sind auch wieder perfect balancierte Bäume und ihre Höhe unterscheidet sich höchstens um '''1'''.&lt;br /&gt;
&lt;br /&gt;
===Größe===&lt;br /&gt;
[[Image:baum-v.png|700px|right]]&lt;br /&gt;
====Größe des vollständigen Baumes====&lt;br /&gt;
Ebene K hat 2 &amp;lt;sup&amp;gt;k&amp;lt;/sup&amp;gt; Knoten.&lt;br /&gt;
Falls Tiefe = d , dann N = 2&amp;lt;sup&amp;gt;0&amp;lt;/sup&amp;gt; + 2&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;.....+ 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; = 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1&lt;br /&gt;
&lt;br /&gt;
====Größe des perfect balancierten Baumes====&lt;br /&gt;
Die Tiefe d, kann nicht besser sein als vollständiger Baum&lt;br /&gt;
:'''=&amp;gt;'''N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2 &amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
;schlechteste perfect balancierter Baum : ist ein vollständiger der Tiefe  d - 1 + 1, N &amp;lt;math&amp;gt; \ge&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:'''=&amp;gt;'''  2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1)   &amp;lt;math&amp;gt;~&amp;lt;&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt;)&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
d &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt;  log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; (d + 1)&lt;br /&gt;
&lt;br /&gt;
*Ergibt die Komplexität der Suche im schlechtesten Fall: Anzahl der Vergleiche pro Knoten( = 2 bzw. = 3)&amp;lt;math&amp;gt;\ast&amp;lt;/math&amp;gt;Anzahl der Knoten&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow ~f(N)\le 2d \le 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
*'''perfekt balancierter Baum '''=&amp;gt; AVL-Baum &lt;br /&gt;
*'''balancierter Baum''':&lt;br /&gt;
::&amp;lt;math&amp;gt;\forall  N:d(N)\le c \ast d (N)&amp;lt;/math&amp;gt; und  &amp;lt;math&amp;gt;1 \le c &amp;lt; \mathcal {1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:: d ist die Tiefe von perfekt balancierten Baum&lt;br /&gt;
&lt;br /&gt;
:''Komplexität der Suche'':&amp;lt;math&amp;gt; ~f(N)\le  c\ast 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
:algorithmisch einfacher als perfekt balancierter Baum, aber fast genauso schnell&lt;br /&gt;
&lt;br /&gt;
====Selbst-balancierter Baum====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  def insertTree(node,key):&lt;br /&gt;
    if node is sentinel:       #(None = sentinel)&lt;br /&gt;
        return Node(key)       &lt;br /&gt;
    if node.key == key:&lt;br /&gt;
        return Node&lt;br /&gt;
    if key &amp;lt; node.key:&lt;br /&gt;
        Node = insertTree(node.left, key)&lt;br /&gt;
    else:&lt;br /&gt;
        node.right = insertTree(node.right, key)&lt;br /&gt;
   #optimiere Balance:&lt;br /&gt;
        return node&lt;br /&gt;
&lt;br /&gt;
===Anderson-Bäume===&lt;br /&gt;
&lt;br /&gt;
  class Node:&lt;br /&gt;
    def__init__(self, key):&lt;br /&gt;
        self.key = key&lt;br /&gt;
        self.left = selft.right = None&lt;br /&gt;
        #einfügen:&lt;br /&gt;
        self.level = 1&lt;br /&gt;
&lt;br /&gt;
:''level'' : kodiert Abstand von sentinel&lt;br /&gt;
====Regeln====&lt;br /&gt;
Es gibt vertikale Kanten(parent.level == child.level + 1 ) und horizontale Kanten(parent.level == child.level)&lt;br /&gt;
:*verfeinerter Regel :  ''level kodiert reduzierten Abstand von Setinel''  (d.b. horizontale Kanten werden nicht gezählt)&lt;br /&gt;
::(die nächste zwei Regeln sichern die Balance):&lt;br /&gt;
::*alle RS-Pfade haben die gleiche reduzierte Länge oder ''root'' hat bei allen Pfaden das gleiche Level&lt;br /&gt;
::*kein Pfad hat 2 aufeinanderer folgende horizontale Kanten&lt;br /&gt;
:*nur Kanten zum rechten Kind dürfen horizontal sein&lt;br /&gt;
:*die reduzierte Höhe jedes Blatts ist '''hr=1'''&lt;br /&gt;
&lt;br /&gt;
====Beweis====&lt;br /&gt;
Vereinfachung des Algorithmus&lt;br /&gt;
:'''Satz''':ein Anderson-Baum ist balanciert.&lt;br /&gt;
*1. Sei '''hr'''- die reduzierte Höhe&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; jeder Teilbaum enthält mindestens &amp;lt;math&amp;gt;N\ge 2^{hr} -1 &amp;lt;/math&amp;gt;  Knoten&lt;br /&gt;
&lt;br /&gt;
::a). Blätter : reduzierte Höhe 1 =&amp;gt; &amp;lt;math&amp;gt; N\ge 2^{1} - 1 = 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
::b). inneren Knoten: jeder Unterbaum hat mindestens reduzierte Höhe &amp;lt;math&amp;gt; ~hr - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt;  jeder Unterbaum hat mindestens &amp;lt;math&amp;gt; ~2^{hr} -1 &amp;lt;/math&amp;gt; Knoten&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt;N \ge 2 (2^{hr-1} -1)+1 = 2^w - 2 + 1 = 2^{hr} -1&amp;lt;/math&amp;gt;&lt;br /&gt;
::::alle RS-Pfade heben gleiche Länge&lt;br /&gt;
[[Image:Anderson.png|700px|right]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
*2. Kein Pfad hat 2 aufeinanderfolgende horizontal Kanten&lt;br /&gt;
:::&amp;lt;math&amp;gt;\Rightarrow  d \le 2 hr + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
*3. zusammen:&lt;br /&gt;
:&amp;lt;math&amp;gt;N \ge 2^{\frac {d}{2}} - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;log_{2}{ N}+1\ge log 2^{\frac {d}{2}} - 1 \ge \frac {d}{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; ~d &amp;lt; 2 log_{2}{(N + 1)}&amp;lt;/math&amp;gt;     - balancierter Baum&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
:'''Suchzeit''' : &amp;lt;math&amp;gt; ~ f(N) = \mathcal{O}(log{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Wie erreicht man die Balance===&lt;br /&gt;
====Rotation====&lt;br /&gt;
[[Image:Abbildung4.jpg|350px|Illustration von Rotation]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
   def rotateRight (node):&lt;br /&gt;
      root = node.left&lt;br /&gt;
      node.left = node.right&lt;br /&gt;
      root.right = node&lt;br /&gt;
      return root&lt;br /&gt;
   def rotateLeft(node):&lt;br /&gt;
      root = node.right&lt;br /&gt;
      node.right = node.left&lt;br /&gt;
      root.left = node&lt;br /&gt;
      return root&lt;br /&gt;
&lt;br /&gt;
====Optimierung der Balance====&lt;br /&gt;
   &lt;br /&gt;
&lt;br /&gt;
       if node.left is not sentinel and node.level==node.left.level:&lt;br /&gt;
            node &amp;gt; rotateRight(node)&lt;br /&gt;
       if node.right is not sentinel and node.right.right is  not sentinel and node.level==rotate.right.right.level:&lt;br /&gt;
            node = rotateLeft(node)&lt;br /&gt;
            node.level += 1&lt;/div&gt;</summary>
		<author><name>Nata</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1488</id>
		<title>Suchen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1488"/>
				<updated>2008-05-29T00:14:29Z</updated>
		
		<summary type="html">&lt;p&gt;Nata: /* Rotation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Es wäre super wenn jemand ganz kurz schreiben könnte, was am Donnerstag zu den Funktionen treeInsert/Remove/HasKey gemacht wurde, was man ja für den aktuellen Übungszettel braucht. Danke :-)&amp;lt;br /&amp;gt;&lt;br /&gt;
/edit: an eine Funktion &amp;quot;HasKey&amp;quot; kann ich mich aus der Vorlesung nicht erinnern. Wenn die jemand hat, bitte eintragen. - Protokollant&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--Hallo, ich kenne leider deinen Namen nicht, ich habe den Wiki-Eintrag von letztem Donnerstag gemacht.Da das Thema 'Dynamisches Array' noch Thema von letzter Woche war, hab ich die Wiederholung der amortisierten Kosten von diesem Mittwoch noch bei Effizienz eingetragen, du musst es also nicht noch mal in deinem Wiki eintragen. Du kannst dir aber gerne mal anschauen was ich geschrieben habe, vielleicht fallen dir noch ein paar Sachen ein, die man hätte besser machen können. lg, Franziska.--&amp;gt;&lt;br /&gt;
Das Suchen ist eine grundlegende Operation in der Informatik. Viele Probleme in der Informatik können auf Suchaufgaben zurückgeführt werden.&lt;br /&gt;
&lt;br /&gt;
Gemeint ist mit Suchen das Wiederauffinden einer oder mehrerer Datensätze aus einer Menge von früher gespeicherten Datensätzen. Ein paar einleitende Worte zum Suchproblem findet man [http://de.wikipedia.org/wiki/Suche hier].&lt;br /&gt;
&lt;br /&gt;
== Überblick verschiedener Suchmethoden ==&lt;br /&gt;
&lt;br /&gt;
Um sich der Vielseitigkeit von Suchproblemen bewusst zu werden, ist es sinnvoll, sich einen Überblick über verschiedene Suchmethoden zu verschaffen. &lt;br /&gt;
&lt;br /&gt;
Hier sei auch auf einen bereits existierenden Wikipedia-Artikel zu [http://de.wikipedia.org/wiki/Suchverfahren Suchverfahren] verwiesen.&lt;br /&gt;
&lt;br /&gt;
Allen gemeinsam ist die grundlegende Aufgabe, ein Datenelement mit bestimmten Eigenschaften aus einer großen Menge von Datenelementen zu selektieren.&lt;br /&gt;
Dies kann, natürlich ohne jeden Anspruch auf Vollständigkeit, nach einer der jetzt diskutierten Methoden geschehen:&lt;br /&gt;
&lt;br /&gt;
* '''Schlüsselsuche''': meint das Suchen von Elementen mit bestimmtem Schlüssel; ein klassisches Beispiel wäre das Suchen in einem Wörterbuch,  die Schlüssel entsprechen hier den Wörtern, die Datensätze wären die zu den Wörtern gehörigen Eintragungen.&lt;br /&gt;
&lt;br /&gt;
* '''Bereichssuche''': Im Allgemeinen meint die Bereichssuche in n-Dimensionen die Selektion von Elementen mit Eigenschaften aus einem bestimmten n-dimensionalen Volumen. Im eindimensionalen Fall will man alle Elemente finden, deren Eigenschaft(en) in einem bestimmten Intervall liegen. Die Verallgemeinerung auf n-Dimensionen ist offensichtlich. Ein Beispiel für die Bereichssuche in einer 3D-Kugel wäre ein Handy mit Geolokalisierung, welches alle Restaurants in einem Umkreis von 500m findet. Lineare Ungleichungen werden graphisch durch [http://de.wikipedia.org/wiki/Hyperebene Hyperebenen] repräsentiert. In 2D sind diese Hyperebenen Geraden. Die Ungleichungen können dann den Lösungsraum in irgendeiner Form begrenzen.&lt;br /&gt;
&lt;br /&gt;
* '''Ähnlichkeitssuche''': Finde Elemente, die gegebenen Eigenschaften möglichst ähnlich sind. Ein prominentes Beispiel ist Google (=Ähnlichkeit zwischen Suchbegriffen und Dokumenten) oder das Suchen des nächstengelegenen Restaurants (Ähnlichkeit zwischen eigener Position und Position des Restaurants). Ein wichtigster Spezialfall ist die ''nächste-nachbar Suche''.&lt;br /&gt;
&lt;br /&gt;
* '''Graphensuche''': Hier wäre beispielsweise das Problem optimaler Wege zu nennen (Navigationssuche). Dieser Punkt wird später im Verlauf der Vorlesung noch einmal aufgegriffen werden.&lt;br /&gt;
&lt;br /&gt;
Im jetzt Folgenden wird nur noch die ''Schlüsselsuche'' betrachtet werden.&lt;br /&gt;
&lt;br /&gt;
==Sequentielle Suche==&lt;br /&gt;
&lt;br /&gt;
Die ''sequentielle'' oder ''lineare'' Suche ist die 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 = sequentialSearch(a, key) &lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq &amp;lt;/math&amp;gt; foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def sequentialSearch(a, key):&lt;br /&gt;
    for i in range(len(a)):&lt;br /&gt;
        if a[i] == key:  # bzw. allgemeiner a[i].key == key &lt;br /&gt;
            return i&lt;br /&gt;
    return -1&lt;br /&gt;
&lt;br /&gt;
Wir wollen jetzt die Komplexität dieses Algorithmus bestimmen, wobei die Problemgröße durch &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt; gegeben ist. &lt;br /&gt;
&lt;br /&gt;
Dabei nimmt man an, dass der innerste Vergleich (a[i] == key) jeweils &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt; ist (diese Annahme könnte verletzt sein, wenn der Vergleichsoperator überladen ist und dadurch eine höhere Komplexität hat). Dieser Vergleich wird in der for-Schleife jeweils N-mal durchgeführt (&amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt;), so dass man nach der Verschachtelungsregel eine gesamte Komplexität von &amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt; erhält.&lt;br /&gt;
&lt;br /&gt;
Der Name ''lineare'' Suche rührt von diesem linearen Anwachsen der Komplexität mit der Arraygröße her.&lt;br /&gt;
&lt;br /&gt;
==Binäre Suche==&lt;br /&gt;
&lt;br /&gt;
Wie wir weiter unten zeigen werden, gestattet es diese Suchmethode, die Gesamtdauer der Suche in großen Datensätzen beträchtlich zu verringern. Die Methode beruht auf dem [http://de.wikipedia.org/wiki/Divide_and_Conquer Divide and Conquer-Prinzip], wobei die Suche in jedem Schritt rekursiv auf eine Hälfte des Datensatzes eingeschränkt wird. Weitere Details zur Methode sind [http://de.wikipedia.org/wiki/Bin%C3%A4re_Suche hier] zu finden. &lt;br /&gt;
&lt;br /&gt;
Die Methode ist nur dann anwendbar beziehungsweise effektiv, wenn folgendes gilt:&lt;br /&gt;
&lt;br /&gt;
# Auf der Eigenschaft der Daten, die zur Suche verwendet wird, ist eine Ordnung im Sinne von &amp;lt; oder &amp;gt; definiert.&lt;br /&gt;
# Wir wollen uns auf Datensätze beschränken, die schon fertig aufgebaut sind, in die also keine neuen Elemente mehr eingefügt werden, wenn man mit dem Suchen beginnt. Ist dies nicht der Fall, so ist unter Umständen die Implementierung über einen [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum Binärbaum] (siehe auch weiter unten) geschickter. &lt;br /&gt;
&lt;br /&gt;
Der folgende Algorithmus zeigt eine beispielhafte Implementierung der Methode (wir setzen wieder &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt;):&lt;br /&gt;
&lt;br /&gt;
 a = [...,...]     # array&lt;br /&gt;
 a.sort()   # sortiere über Ordnung des Schlüssels&lt;br /&gt;
 foundIndex = binSearch(a, key, 0, len(a))  # (Array, Schlüssel, von wo bis wo suchen im Array)&lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq&amp;lt;/math&amp;gt;  foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def binSearch(a, key, start, end):  # start ist 1. Index, end ist letzter Index + 1&lt;br /&gt;
    size = end - start   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if size &amp;lt;= 0:   # Bereich leer?  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return -1   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    center = (start + end)/2   # Integer Division, Ergebnis wird abgerundet, wichtig für ganzzahlige Indizes &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if a[center] == key:  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return center  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    elif a[center] &amp;lt; key:  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return binSearch(a, key, center + 1, end)  # Rekursion&lt;br /&gt;
    else:&lt;br /&gt;
        return binSearch(a, key, start + 1, center)  # Rekursion&lt;br /&gt;
&lt;br /&gt;
Zur Berechnung der Komplexität dieses Algorithmus vernachlässigen wir zunächst den Aufwand, den die Sortierung weiter oben verursacht. Dieser Schritt mag oder mag nicht zulässig sein.&lt;br /&gt;
&lt;br /&gt;
Nach der Sequenzregel haben auch alle &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;.  Es bleibt die Komplexität der Rekursion zu berechnen. Die gesamte Komplexität des Algorithmus (jetzt als Funktion f bezeichnet) setzt sich zusammen aus den oben erwähnten &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen sowie der Rekursion und ist &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f(N) = \mathcal{O}(1) + f(N/2) = \mathcal{O}(1) + \mathcal{O}(1) + f(N/4) = ... = \underbrace{\mathcal{O}(1) + ... + \mathcal{O}(1) + \underbrace{f(0)}_{\mathcal{O}(1)\, \rightarrow \,\mathrm{size-Abfrage}}}_{n+1 \,\mathrm{Terme}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Falls jetzt gilt &amp;lt;math&amp;gt; N = 2^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \rightarrow f(N) = \mathcal{O}(1) \cdot \mathcal{O}(n+1) = \mathcal{O}(n) = \mathcal{O}(\lg N) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Für große Datenmengen ist die ''binäre Suche'' also weit effizienter als die ''lineare Suche''. Verdoppelt sich beispielsweise die zu durchsuchende Datenmenge, so verdoppelt sich der Aufwand für die ''sequentielle Suche'' - bei der ''binären Suche'' hingegen benötigt man lediglich eine zusätzliche Vergleichsoperation. &lt;br /&gt;
&lt;br /&gt;
Für kleine Daten (&amp;lt;math&amp;gt; N = 4,\, 5 &amp;lt;/math&amp;gt;) ist die ''sequentielle Suche'' jedoch schneller als die ''binäre Suche'', da hier die rekursiven Funktionsaufrufe teurer als das Mehr an Vergleichen sind. Ein anderer ungünstiger Fall ist gegeben, wenn nur sehr wenige Suchanfragen erfolgen (weniger als &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; viele). Dann wird der Aufwand durch das Sortieren des Arrays dominiert, ist also &amp;lt;math&amp;gt;\mathcal{O}(N \lg N) &amp;lt;/math&amp;gt;. Auch dann ist sequentielle Suche vorzuziehen.&lt;br /&gt;
&lt;br /&gt;
Eine relativ einfache Möglichkeit, die ''binäre Suche'' zu verbessern, ist die sogenannte ''Interpolationssuche''. Hierbei wird die neue Position für die Suche, also die Mitte des Arrays, durch eine Schätzung ersetzt, die angibt, wo sich der Schlüssel innerhalb des Arrays befinden könnte. Bei der Suche in einem Telefonbuch nach dem Namen Zebra würde man ja auch nicht in der Mitte anfangen. Näheres hierzu im Buch von ''Sedgewick''.&lt;br /&gt;
&lt;br /&gt;
Um sich den Algorithmus der ''binären Suche'' klar zu machen, ist es instruktiv, sich die folgende Tabelle genauer anzusehen, die die sukzessive Belegung der Variablen bei verschiedenen Anfragen beschreibt. Die Testfälle wurden nach dem Prinzip des ''domain partitioning'' gewählt. Das zugehörige Array hat die Einträge&lt;br /&gt;
&lt;br /&gt;
 a = [2, 3, 4, 5, 6]&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align:center&amp;quot; border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;5&amp;quot; cellspacing=&amp;quot;0&amp;quot; &lt;br /&gt;
! gesuchter key   !!  start      !! end  !! size !! center !! return &amp;lt;br/&amp;gt; (-1 oder index)  !! Kommentare  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 4     ||0            || 5    ||  5   || 2   ||  2         || gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 2     || 0           || 5    ||  5   || 2   ||            || linker Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||           &lt;br /&gt;
|-&lt;br /&gt;
|       ||  0          || 1    ||  1   || 0   ||  0         || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 1     ||0            || 5    ||  5   || 2   ||            || links außerhalb&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 1    ||  1   || 0   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 0    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 6     ||0            || 5    ||  5   || 2   ||            || rechter Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    || 2    || 4   || 4          || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 5     ||0            || 5    ||  5   || 2   ||            || typischer Fall&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||3            || 5    ||  2   || 4   ||            ||  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       || 3           || 4    ||  1   || 3   || 3          || gefunden&lt;br /&gt;
|- &lt;br /&gt;
| 7     ||0            || 5    ||  5   || 2   ||            || rechts außerhalb        &lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    ||  2   || 4   ||            ||&lt;br /&gt;
|-&lt;br /&gt;
|       ||5            || 5    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Suche in einem Binärbaum ==&lt;br /&gt;
&lt;br /&gt;
Eine kurze Einführung in Binärbäume findet man [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum hier].&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum.png|text-top|300x300px|Zur Illustration von Bäumen]]&lt;br /&gt;
&lt;br /&gt;
Bäume sind zweidimensional verkettete Strukturen. Sie gehören zu den fundamentalen Datenstrukturen in der Informatik. Da man in Bäumen nicht nur Daten speichern kann, sondern auch relevante Beziehungen der Daten untereinander, festgelegt über eine Ordnung auf der vergleichenden Dateneigenschaft (''Schlüssel''), eignen sich Bäume also insbesondere, um gesuchte Daten schnell wieder auffinden zu können.&lt;br /&gt;
&lt;br /&gt;
Ein ''Binärbaum'' wie oben skizziert besteht aus einer Menge von ''Knoten'', die untereinander durch ''Kanten'' verbunden sind. Jeder Knoten hat einen linken und einen rechten Unterbaum, der auch leer sein kann (in Python ließe sich dies mit ''None'' implementieren). Führt eine Kante von Knoten A zu Knoten B, so heißt A Vater von B und B Kind von A. Es gibt genau einen Knoten ohne Vater, den man ''Wurzel'' nennt. Knoten ohne Kinder heißen ''Blätter''.&lt;br /&gt;
&lt;br /&gt;
Ein ''Suchbaum'' hat zusätzlich die Eigenschaft, dass die Schlüssel jedes Knotens sortiert sind. Alle Schlüssel im linken Unterbaum sind kleiner, alle Schlüssel im rechten Unterbaum sind größer als ihr Vater. Wir wollen hierbei annehmen, dass jeder Schlüssel pro Datensatz nur einmal vorkommt, da sich sonst die &amp;gt;- oder &amp;lt;-Relation nicht mehr strikt erfüllen ließe.&lt;br /&gt;
&lt;br /&gt;
Um in einem Baum suchen zu können, wollen wir von zwei Annahmen ausgehen:&lt;br /&gt;
# Einfügen und Suchen im Baum wechseln sich ab. (Wenn das Suchen erst beginnt, nachdem alle Einfügungen erfolgt sind, wäre ein dynamisches Array mit binärer Suche wie oben wesentlich einfacher.)&lt;br /&gt;
# Der Schlüssel, der die Anordnung bestimmt, kennt eine [http://de.wikipedia.org/wiki/Ordnungsrelation Ordnung] (&amp;lt;-Relation oder &amp;gt;-Relation).&lt;br /&gt;
&lt;br /&gt;
Der folgende Python-Code zeigt beispielhaft, wie man in einem Suchbaum suchen könnte. Der Konstruktor für einen Knoten des Suchbaums ließe sich zum Beispiel so implementieren:&lt;br /&gt;
 &lt;br /&gt;
 class Node:&lt;br /&gt;
     def __init__(self, key):&lt;br /&gt;
         self.key = key&lt;br /&gt;
         self.left = self.right = None&lt;br /&gt;
 &lt;br /&gt;
 root = ...    # Wurzel des Suchbaums&lt;br /&gt;
 nodeFound = treeSearch(root, key)   # None, falls nichts gefunden&lt;br /&gt;
 &lt;br /&gt;
 def treeSearch(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return None&lt;br /&gt;
     elif node.key == key:&lt;br /&gt;
         return node&lt;br /&gt;
     elif key &amp;lt; node.key:&lt;br /&gt;
         return treeSearch(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         return treeSearch(node.right, key)&lt;br /&gt;
&lt;br /&gt;
Daraus resultiert der folgende Suchalgorithmus:&lt;br /&gt;
&lt;br /&gt;
 def treeSort(node,array):     # dynamisches Array als 2. Argument&lt;br /&gt;
     if node is None:     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
         return None:&lt;br /&gt;
     treeSort(node.left, array)     # rekursiv&lt;br /&gt;
     array.append(node.key)     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
     treeSort(node.right, array)     # rekursiv&lt;br /&gt;
&lt;br /&gt;
Komplexität:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 f(N)=\mathcal{O}(1)+f(N_\mathrm{left})+f(N_\mathrm{right})=\mathcal{O}(1)+\mathcal{O}(1)+f(N_\mathrm{leftleft})+f(N_\mathrm{leftright})+\mathcal{O}(1)+f(N_\mathrm{rightleft})&lt;br /&gt;
 +f(N_\mathrm{leftright})=N\ast\mathcal{O}(1)=\mathcal{O}(N)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sortier-Pseudocode:&lt;br /&gt;
&lt;br /&gt;
 Sortieren:&lt;br /&gt;
     (Array) a     # unsortiert&lt;br /&gt;
     (tree) t     # zunächst leer&lt;br /&gt;
 (dynamisches Array) r     # später sortiert&lt;br /&gt;
 for e in a:&lt;br /&gt;
     t = treeInsert(t, e)&lt;br /&gt;
 treeSort(t, r)&lt;br /&gt;
&lt;br /&gt;
== Insert ==&lt;br /&gt;
&lt;br /&gt;
Was passiert wenn der key (Schlüssel) schon vorhanden ist?&lt;br /&gt;
* error (exception)&lt;br /&gt;
* nichts einfügen&lt;br /&gt;
* nichts einfügen aber einen boolean return zurückgeben (einfügen=true, false)&lt;br /&gt;
* nochmals eingefügt (z.B. in der Node Klasse)&lt;br /&gt;
&lt;br /&gt;
Wobei die ersten 3 Punkte zur Mengensemantik gehören und der letzte eine Multimenge ist.&lt;br /&gt;
&lt;br /&gt;
Algorithmus im zweiten Fall (nichts einfügen):&lt;br /&gt;
&lt;br /&gt;
 def treeInsert(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return Node(key)     # Alternative Schreibweise: node = Node(key)&lt;br /&gt;
     if node.key == key:      # und dann:&lt;br /&gt;
         return node          # pass&lt;br /&gt;
     elif key &amp;lt; node.key:     # links im Baum&lt;br /&gt;
         node.left = treeInsert(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         node.right = treeInsert(node.right, key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Remove ==&lt;br /&gt;
Fälle:&lt;br /&gt;
# key (bzw. Knoten der key enthält) ist ein Blatt =&amp;gt; einfach löschen&lt;br /&gt;
# node hat &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; linken Unterbaum oder &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; rechten Unterbaum =&amp;gt; durch Unterbaum ersetzen&lt;br /&gt;
# node hat beide Unterbäume:&lt;br /&gt;
#* Suche Vorgänger: max k &amp;lt; key (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; keys); Vorgänger ist immer ein Fall 1 oder Fall 2&lt;br /&gt;
#*:=&amp;gt; ersetze node durch Vorgänger und entferne Vorgänger&lt;br /&gt;
&lt;br /&gt;
 def treePredecessor(node):     # wird nur bei Fall 3 aufgerufen&lt;br /&gt;
     node = node.left&lt;br /&gt;
     while node.right is not None:&lt;br /&gt;
         node = node.right&lt;br /&gt;
     return node&lt;br /&gt;
 &lt;br /&gt;
 def treeRemove(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return node&lt;br /&gt;
     if key &amp;lt; node.key:&lt;br /&gt;
         node.left = treeRemove(node.left, key)&lt;br /&gt;
     elif key &amp;gt; node.key:&lt;br /&gt;
         node.right = treeRemove(node.right, key)&lt;br /&gt;
     else:&lt;br /&gt;
         if node.left is None and node.right is None:     # Fall 1&lt;br /&gt;
             node = None&lt;br /&gt;
         elif node.left is None:     # Fall 2&lt;br /&gt;
             node = node.right       # +&lt;br /&gt;
         elif node.right is None:    # Fall 2&lt;br /&gt;
             node = node.left&lt;br /&gt;
         else:&lt;br /&gt;
             pred = treePredecessor(node)&lt;br /&gt;
             node.key = pred.key&lt;br /&gt;
             node.left = treeRemove(node.left, pred.key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Komplexitätsanalyse ==&lt;br /&gt;
&lt;br /&gt;
* Pfad (Zwischen node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;): &lt;br /&gt;
**Folge von Knoten (node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt;,...,node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt;), sodass:&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;ki&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;ki+1&amp;lt;/sub&amp;gt; haben eine gemeinsame Kante.&lt;br /&gt;
[[Image:Baum_Pfad.png]]&lt;br /&gt;
(Ein Baum ist ein Graph, indem es zwischen beliebigen Knoten stets genau einen Pfad gibt.)&lt;br /&gt;
&lt;br /&gt;
* Länge eines Pfades: Anzahl der Kanten = Anzahl der Knoten - 1&lt;br /&gt;
* Tiefe eines Knotens: Pfadlänge vom Knoten zur Wurzel des Baumes.&lt;br /&gt;
* Tiefe des Baumes: maximale Tiefe eines Knotens&lt;br /&gt;
* Balance eines Baumes: maximale Tiefe(k) - minimale Tiefe(k) (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; Blätter)&lt;br /&gt;
&lt;br /&gt;
== Ungünstigster Fall von treeSearch ==&lt;br /&gt;
&lt;br /&gt;
Komplexität von treeSearch = Länge des Pfades zum Knoten wo &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; gefunden wird, oder erkannt wird, dass &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; nicht im Baum ist.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; wird nicht gefunden, aber für diese Entscheidung muss der Längste Pfad vollständig durchlaufen werden.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(T)&amp;lt;/math&amp;gt; wo T = Tiefe des Baumes&lt;br /&gt;
*: [1,2,3,4,5]:&amp;lt;br /&amp;gt; [[Image:Balance.png]]&lt;br /&gt;
*: Tiefe T = 4, Balance = 4&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; wo N = Anzahl der Elemente.&lt;br /&gt;
&lt;br /&gt;
== Aufgabe ==&lt;br /&gt;
&lt;br /&gt;
Minimiere Balance (erzeuge balancierten Baum):&lt;br /&gt;
# Einfügen in geschickter Reihenfolge (siehe Übungsaufgabe)&lt;br /&gt;
# Selbstbalancierter Baum:&lt;br /&gt;
#* Überprüfen der Balance nach jedem Einfügen&lt;br /&gt;
#* Umstrukturieren des Baumes, falls Balance &amp;gt; 1 (Suchbaum-Bedingung muss erhalten bleiben)&lt;br /&gt;
#* AVL-Bäume (älteste Variante)&lt;br /&gt;
#* Rot-Schwarz-Bäume (verbreiteste Variante)&lt;br /&gt;
#* Treaps (flexibelste Variante, siehe Übung)&lt;br /&gt;
#* Splay trees&lt;br /&gt;
#* Andersson Trees (einfachste Variante)&lt;br /&gt;
(#* Skip Lists (schnellste Variante, aber kein Binärbaum))&lt;br /&gt;
&lt;br /&gt;
== Umstrukturieren, so dass Suchbaumbedingung erhalten bleibt: ==&lt;br /&gt;
&lt;br /&gt;
Rotation: elementare Umstrukturierungen&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum_Rotation.png]]&lt;br /&gt;
&lt;br /&gt;
 def rotateRight(node):&lt;br /&gt;
     newRoot = node.left&lt;br /&gt;
     node.left = newRoot.right&lt;br /&gt;
     newRoot.right = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
 def rotateLeft(node):&lt;br /&gt;
     newRoot = node.right&lt;br /&gt;
     node.right = newRoot.left&lt;br /&gt;
     newRoot.left = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
== Balance eines Suchbaumes ==&lt;br /&gt;
&lt;br /&gt;
===Balance eines Baumes zu  definieren:===&lt;br /&gt;
[[Image:Abbildung1.jpg|250px|right]]&lt;br /&gt;
*betrachte &amp;lt;tt&amp;gt;None&amp;lt;/tt&amp;gt; als zusätzlichen Knoten : '''sentinel'''   ([[englische Sprache|engl.]] für ''Wächter'')&lt;br /&gt;
*oder defeniere speziellen sentinel-Knoten&lt;br /&gt;
*RS - Pfades : von &amp;lt;tt&amp;gt;root&amp;lt;/tt&amp;gt; &amp;amp;rarr; ''sentinel''&lt;br /&gt;
*Länge eines Pfades ''P''      '''''|P|'''''&lt;br /&gt;
* &amp;lt;u&amp;gt;die Balance eines Baumes zu defenieren:&amp;lt;/u&amp;gt;&lt;br /&gt;
: '''''B = max P '''''&amp;lt;math&amp;gt; \in &amp;lt;/math&amp;gt; '''''{RS}    |P| - min |P|'''''&lt;br /&gt;
:'''''{RS}'''''  Menge aller RS-Pfade&lt;br /&gt;
;vollständiger Baum:  Balance &amp;lt;math&amp;gt;~=0&amp;lt;/math&amp;gt;&lt;br /&gt;
:Alle Knoten(außer Blättern) haben 2 Kinder.&lt;br /&gt;
;perfect balancierter Baum:  Balance  &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 1&lt;br /&gt;
'''alternative Defenition für perfect balancierte Bäume'''&lt;br /&gt;
:Für jeden Teilbaum gilt es: rechtes und linkes Kind (für jeden Knoten) sind auch wieder perfect balancierte Bäume und ihre Höhe unterscheidet sich höchstens um '''1'''.&lt;br /&gt;
&lt;br /&gt;
===Größe===&lt;br /&gt;
[[Image:baum-v.png|700px|right]]&lt;br /&gt;
====Größe des vollständigen Baumes====&lt;br /&gt;
Ebene K hat 2 &amp;lt;sup&amp;gt;k&amp;lt;/sup&amp;gt; Knoten.&lt;br /&gt;
Falls Tiefe = d , dann N = 2&amp;lt;sup&amp;gt;0&amp;lt;/sup&amp;gt; + 2&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;.....+ 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; = 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1&lt;br /&gt;
&lt;br /&gt;
====Größe des perfect balancierten Baumes====&lt;br /&gt;
Die Tiefe d, kann nicht besser sein als vollständiger Baum&lt;br /&gt;
:'''=&amp;gt;'''N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2 &amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
;schlechteste perfect balancierter Baum : ist ein vollständiger der Tiefe  d - 1 + 1, N &amp;lt;math&amp;gt; \ge&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:'''=&amp;gt;'''  2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1)   &amp;lt;math&amp;gt;~&amp;lt;&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt;)&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
d &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt;  log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; (d + 1)&lt;br /&gt;
&lt;br /&gt;
*Ergibt die Komplexität der Suche im schlechtesten Fall: Anzahl der Vergleiche pro Knoten( = 2 bzw. = 3)&amp;lt;math&amp;gt;\ast&amp;lt;/math&amp;gt;Anzahl der Knoten&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow ~f(N)\le 2d \le 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
*'''perfekt balancierter Baum '''=&amp;gt; AVL-Baum &lt;br /&gt;
*'''balancierter Baum''':&lt;br /&gt;
::&amp;lt;math&amp;gt;\forall  N:d(N)\le c \ast d (N)&amp;lt;/math&amp;gt; und  &amp;lt;math&amp;gt;1 \le c &amp;lt; \mathcal {1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:: d ist die Tiefe von perfekt balancierten Baum&lt;br /&gt;
&lt;br /&gt;
:''Komplexität der Suche'':&amp;lt;math&amp;gt; ~f(N)\le  c\ast 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
:algorithmisch einfacher als perfekt balancierter Baum, aber fast genauso schnell&lt;br /&gt;
&lt;br /&gt;
====Selbst-balancierter Baum====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  def insertTree(node,key):&lt;br /&gt;
    if node is sentinel:       #(None = sentinel)&lt;br /&gt;
        return Node(key)       &lt;br /&gt;
    if node.key == key:&lt;br /&gt;
        return Node&lt;br /&gt;
    if key &amp;lt; node.key:&lt;br /&gt;
        Node = insertTree(node.left, key)&lt;br /&gt;
    else:&lt;br /&gt;
        node.right = insertTree(node.right, key)&lt;br /&gt;
   #optimiere Balance:&lt;br /&gt;
        return node&lt;br /&gt;
&lt;br /&gt;
===Anderson-Bäume===&lt;br /&gt;
&lt;br /&gt;
  class Node:&lt;br /&gt;
    def__init__(self, key):&lt;br /&gt;
        self.key = key&lt;br /&gt;
        self.left = selft.right = None&lt;br /&gt;
        #einfügen:&lt;br /&gt;
        self.level = 1&lt;br /&gt;
&lt;br /&gt;
:''level'' : kodiert Abstand von sentinel&lt;br /&gt;
====Regeln====&lt;br /&gt;
Es gibt vertikale Kanten(parent.level == child.level + 1 ) und horizontale Kanten(parent.level == child.level)&lt;br /&gt;
:*verfeinerter Regel :  ''level kodiert reduzierten Abstand von Setinel''  (d.b. horizontale Kanten werden nicht gezählt)&lt;br /&gt;
::(die nächste zwei Regeln sichern die Balance):&lt;br /&gt;
::*alle RS-Pfade haben die gleiche reduzierte Länge oder ''root'' hat bei allen Pfaden das gleiche Level&lt;br /&gt;
::*kein Pfad hat 2 aufeinanderer folgende horizontale Kanten&lt;br /&gt;
:*nur Kanten zum rechten Kind dürfen horizontal sein&lt;br /&gt;
:*die reduzierte Höhe jedes Blatts ist '''hr=1'''&lt;br /&gt;
&lt;br /&gt;
====Beweis====&lt;br /&gt;
Vereinfachung des Algorithmus&lt;br /&gt;
:'''Satz''':ein Anderson-Baum ist balanciert.&lt;br /&gt;
*1. Sei '''hr'''- die reduzierte Höhe&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; jeder Teilbaum enthält mindestens &amp;lt;math&amp;gt;N\ge 2^{hr} -1 &amp;lt;/math&amp;gt;  Knoten&lt;br /&gt;
&lt;br /&gt;
::a). Blätter : reduzierte Höhe 1 =&amp;gt; &amp;lt;math&amp;gt; N\ge 2^{1} - 1 = 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
::b). inneren Knoten: jeder Unterbaum hat mindestens reduzierte Höhe &amp;lt;math&amp;gt; ~hr - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt;  jeder Unterbaum hat mindestens &amp;lt;math&amp;gt; ~2^{hr} -1 &amp;lt;/math&amp;gt; Knoten&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt;N \ge 2 (2^{hr-1} -1)+1 = 2^w - 2 + 1 = 2^{hr} -1&amp;lt;/math&amp;gt;&lt;br /&gt;
::::alle RS-Pfade heben gleiche Länge&lt;br /&gt;
[[Image:Anderson.png|700px|right]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
*2. Kein Pfad hat 2 aufeinanderfolgende horizontal Kanten&lt;br /&gt;
:::&amp;lt;math&amp;gt;\Rightarrow  d \le 2 hr + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
*3. zusammen:&lt;br /&gt;
:&amp;lt;math&amp;gt;N \ge 2^{\frac {d}{2}} - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;log_{2}{ N}+1\ge log 2^{\frac {d}{2}} - 1 \ge \frac {d}{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; ~d &amp;lt; 2 log_{2}{(N + 1)}&amp;lt;/math&amp;gt;     - balancierter Baum&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
:'''Suchzeit''' : &amp;lt;math&amp;gt; ~ f(N) = \mathcal{O}(log{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Wie erreicht man die Balance===&lt;br /&gt;
====Rotation====&lt;br /&gt;
[[Image:Abbildung4.jpg|350px|Illustration von Rotation]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
   def rotateRight (node):&lt;br /&gt;
      root = node.left&lt;br /&gt;
      node.left = node.right&lt;br /&gt;
      root.right = node&lt;br /&gt;
      return root&lt;br /&gt;
   def rotateLeft(node):&lt;br /&gt;
      root = node.right&lt;br /&gt;
      node.right = node.left&lt;br /&gt;
      root.left = node&lt;br /&gt;
      return root&lt;br /&gt;
&lt;br /&gt;
====Optimierung der Balance====&lt;br /&gt;
   &lt;br /&gt;
&lt;br /&gt;
       if node.left is not sentinel and node.level==node.left.level:&lt;br /&gt;
            node &amp;gt; rotateRight(node)&lt;br /&gt;
       if node.right is not sentinel and node.right.right is  not sentinel and node.level==rotate.right.right.level:&lt;br /&gt;
            node = rotateLeft(node)&lt;br /&gt;
            node.level += 1&lt;/div&gt;</summary>
		<author><name>Nata</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1487</id>
		<title>Suchen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1487"/>
				<updated>2008-05-29T00:14:03Z</updated>
		
		<summary type="html">&lt;p&gt;Nata: /* Rotation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Es wäre super wenn jemand ganz kurz schreiben könnte, was am Donnerstag zu den Funktionen treeInsert/Remove/HasKey gemacht wurde, was man ja für den aktuellen Übungszettel braucht. Danke :-)&amp;lt;br /&amp;gt;&lt;br /&gt;
/edit: an eine Funktion &amp;quot;HasKey&amp;quot; kann ich mich aus der Vorlesung nicht erinnern. Wenn die jemand hat, bitte eintragen. - Protokollant&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--Hallo, ich kenne leider deinen Namen nicht, ich habe den Wiki-Eintrag von letztem Donnerstag gemacht.Da das Thema 'Dynamisches Array' noch Thema von letzter Woche war, hab ich die Wiederholung der amortisierten Kosten von diesem Mittwoch noch bei Effizienz eingetragen, du musst es also nicht noch mal in deinem Wiki eintragen. Du kannst dir aber gerne mal anschauen was ich geschrieben habe, vielleicht fallen dir noch ein paar Sachen ein, die man hätte besser machen können. lg, Franziska.--&amp;gt;&lt;br /&gt;
Das Suchen ist eine grundlegende Operation in der Informatik. Viele Probleme in der Informatik können auf Suchaufgaben zurückgeführt werden.&lt;br /&gt;
&lt;br /&gt;
Gemeint ist mit Suchen das Wiederauffinden einer oder mehrerer Datensätze aus einer Menge von früher gespeicherten Datensätzen. Ein paar einleitende Worte zum Suchproblem findet man [http://de.wikipedia.org/wiki/Suche hier].&lt;br /&gt;
&lt;br /&gt;
== Überblick verschiedener Suchmethoden ==&lt;br /&gt;
&lt;br /&gt;
Um sich der Vielseitigkeit von Suchproblemen bewusst zu werden, ist es sinnvoll, sich einen Überblick über verschiedene Suchmethoden zu verschaffen. &lt;br /&gt;
&lt;br /&gt;
Hier sei auch auf einen bereits existierenden Wikipedia-Artikel zu [http://de.wikipedia.org/wiki/Suchverfahren Suchverfahren] verwiesen.&lt;br /&gt;
&lt;br /&gt;
Allen gemeinsam ist die grundlegende Aufgabe, ein Datenelement mit bestimmten Eigenschaften aus einer großen Menge von Datenelementen zu selektieren.&lt;br /&gt;
Dies kann, natürlich ohne jeden Anspruch auf Vollständigkeit, nach einer der jetzt diskutierten Methoden geschehen:&lt;br /&gt;
&lt;br /&gt;
* '''Schlüsselsuche''': meint das Suchen von Elementen mit bestimmtem Schlüssel; ein klassisches Beispiel wäre das Suchen in einem Wörterbuch,  die Schlüssel entsprechen hier den Wörtern, die Datensätze wären die zu den Wörtern gehörigen Eintragungen.&lt;br /&gt;
&lt;br /&gt;
* '''Bereichssuche''': Im Allgemeinen meint die Bereichssuche in n-Dimensionen die Selektion von Elementen mit Eigenschaften aus einem bestimmten n-dimensionalen Volumen. Im eindimensionalen Fall will man alle Elemente finden, deren Eigenschaft(en) in einem bestimmten Intervall liegen. Die Verallgemeinerung auf n-Dimensionen ist offensichtlich. Ein Beispiel für die Bereichssuche in einer 3D-Kugel wäre ein Handy mit Geolokalisierung, welches alle Restaurants in einem Umkreis von 500m findet. Lineare Ungleichungen werden graphisch durch [http://de.wikipedia.org/wiki/Hyperebene Hyperebenen] repräsentiert. In 2D sind diese Hyperebenen Geraden. Die Ungleichungen können dann den Lösungsraum in irgendeiner Form begrenzen.&lt;br /&gt;
&lt;br /&gt;
* '''Ähnlichkeitssuche''': Finde Elemente, die gegebenen Eigenschaften möglichst ähnlich sind. Ein prominentes Beispiel ist Google (=Ähnlichkeit zwischen Suchbegriffen und Dokumenten) oder das Suchen des nächstengelegenen Restaurants (Ähnlichkeit zwischen eigener Position und Position des Restaurants). Ein wichtigster Spezialfall ist die ''nächste-nachbar Suche''.&lt;br /&gt;
&lt;br /&gt;
* '''Graphensuche''': Hier wäre beispielsweise das Problem optimaler Wege zu nennen (Navigationssuche). Dieser Punkt wird später im Verlauf der Vorlesung noch einmal aufgegriffen werden.&lt;br /&gt;
&lt;br /&gt;
Im jetzt Folgenden wird nur noch die ''Schlüsselsuche'' betrachtet werden.&lt;br /&gt;
&lt;br /&gt;
==Sequentielle Suche==&lt;br /&gt;
&lt;br /&gt;
Die ''sequentielle'' oder ''lineare'' Suche ist die 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 = sequentialSearch(a, key) &lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq &amp;lt;/math&amp;gt; foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def sequentialSearch(a, key):&lt;br /&gt;
    for i in range(len(a)):&lt;br /&gt;
        if a[i] == key:  # bzw. allgemeiner a[i].key == key &lt;br /&gt;
            return i&lt;br /&gt;
    return -1&lt;br /&gt;
&lt;br /&gt;
Wir wollen jetzt die Komplexität dieses Algorithmus bestimmen, wobei die Problemgröße durch &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt; gegeben ist. &lt;br /&gt;
&lt;br /&gt;
Dabei nimmt man an, dass der innerste Vergleich (a[i] == key) jeweils &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt; ist (diese Annahme könnte verletzt sein, wenn der Vergleichsoperator überladen ist und dadurch eine höhere Komplexität hat). Dieser Vergleich wird in der for-Schleife jeweils N-mal durchgeführt (&amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt;), so dass man nach der Verschachtelungsregel eine gesamte Komplexität von &amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt; erhält.&lt;br /&gt;
&lt;br /&gt;
Der Name ''lineare'' Suche rührt von diesem linearen Anwachsen der Komplexität mit der Arraygröße her.&lt;br /&gt;
&lt;br /&gt;
==Binäre Suche==&lt;br /&gt;
&lt;br /&gt;
Wie wir weiter unten zeigen werden, gestattet es diese Suchmethode, die Gesamtdauer der Suche in großen Datensätzen beträchtlich zu verringern. Die Methode beruht auf dem [http://de.wikipedia.org/wiki/Divide_and_Conquer Divide and Conquer-Prinzip], wobei die Suche in jedem Schritt rekursiv auf eine Hälfte des Datensatzes eingeschränkt wird. Weitere Details zur Methode sind [http://de.wikipedia.org/wiki/Bin%C3%A4re_Suche hier] zu finden. &lt;br /&gt;
&lt;br /&gt;
Die Methode ist nur dann anwendbar beziehungsweise effektiv, wenn folgendes gilt:&lt;br /&gt;
&lt;br /&gt;
# Auf der Eigenschaft der Daten, die zur Suche verwendet wird, ist eine Ordnung im Sinne von &amp;lt; oder &amp;gt; definiert.&lt;br /&gt;
# Wir wollen uns auf Datensätze beschränken, die schon fertig aufgebaut sind, in die also keine neuen Elemente mehr eingefügt werden, wenn man mit dem Suchen beginnt. Ist dies nicht der Fall, so ist unter Umständen die Implementierung über einen [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum Binärbaum] (siehe auch weiter unten) geschickter. &lt;br /&gt;
&lt;br /&gt;
Der folgende Algorithmus zeigt eine beispielhafte Implementierung der Methode (wir setzen wieder &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt;):&lt;br /&gt;
&lt;br /&gt;
 a = [...,...]     # array&lt;br /&gt;
 a.sort()   # sortiere über Ordnung des Schlüssels&lt;br /&gt;
 foundIndex = binSearch(a, key, 0, len(a))  # (Array, Schlüssel, von wo bis wo suchen im Array)&lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq&amp;lt;/math&amp;gt;  foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def binSearch(a, key, start, end):  # start ist 1. Index, end ist letzter Index + 1&lt;br /&gt;
    size = end - start   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if size &amp;lt;= 0:   # Bereich leer?  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return -1   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    center = (start + end)/2   # Integer Division, Ergebnis wird abgerundet, wichtig für ganzzahlige Indizes &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if a[center] == key:  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return center  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    elif a[center] &amp;lt; key:  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return binSearch(a, key, center + 1, end)  # Rekursion&lt;br /&gt;
    else:&lt;br /&gt;
        return binSearch(a, key, start + 1, center)  # Rekursion&lt;br /&gt;
&lt;br /&gt;
Zur Berechnung der Komplexität dieses Algorithmus vernachlässigen wir zunächst den Aufwand, den die Sortierung weiter oben verursacht. Dieser Schritt mag oder mag nicht zulässig sein.&lt;br /&gt;
&lt;br /&gt;
Nach der Sequenzregel haben auch alle &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;.  Es bleibt die Komplexität der Rekursion zu berechnen. Die gesamte Komplexität des Algorithmus (jetzt als Funktion f bezeichnet) setzt sich zusammen aus den oben erwähnten &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen sowie der Rekursion und ist &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f(N) = \mathcal{O}(1) + f(N/2) = \mathcal{O}(1) + \mathcal{O}(1) + f(N/4) = ... = \underbrace{\mathcal{O}(1) + ... + \mathcal{O}(1) + \underbrace{f(0)}_{\mathcal{O}(1)\, \rightarrow \,\mathrm{size-Abfrage}}}_{n+1 \,\mathrm{Terme}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Falls jetzt gilt &amp;lt;math&amp;gt; N = 2^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \rightarrow f(N) = \mathcal{O}(1) \cdot \mathcal{O}(n+1) = \mathcal{O}(n) = \mathcal{O}(\lg N) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Für große Datenmengen ist die ''binäre Suche'' also weit effizienter als die ''lineare Suche''. Verdoppelt sich beispielsweise die zu durchsuchende Datenmenge, so verdoppelt sich der Aufwand für die ''sequentielle Suche'' - bei der ''binären Suche'' hingegen benötigt man lediglich eine zusätzliche Vergleichsoperation. &lt;br /&gt;
&lt;br /&gt;
Für kleine Daten (&amp;lt;math&amp;gt; N = 4,\, 5 &amp;lt;/math&amp;gt;) ist die ''sequentielle Suche'' jedoch schneller als die ''binäre Suche'', da hier die rekursiven Funktionsaufrufe teurer als das Mehr an Vergleichen sind. Ein anderer ungünstiger Fall ist gegeben, wenn nur sehr wenige Suchanfragen erfolgen (weniger als &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; viele). Dann wird der Aufwand durch das Sortieren des Arrays dominiert, ist also &amp;lt;math&amp;gt;\mathcal{O}(N \lg N) &amp;lt;/math&amp;gt;. Auch dann ist sequentielle Suche vorzuziehen.&lt;br /&gt;
&lt;br /&gt;
Eine relativ einfache Möglichkeit, die ''binäre Suche'' zu verbessern, ist die sogenannte ''Interpolationssuche''. Hierbei wird die neue Position für die Suche, also die Mitte des Arrays, durch eine Schätzung ersetzt, die angibt, wo sich der Schlüssel innerhalb des Arrays befinden könnte. Bei der Suche in einem Telefonbuch nach dem Namen Zebra würde man ja auch nicht in der Mitte anfangen. Näheres hierzu im Buch von ''Sedgewick''.&lt;br /&gt;
&lt;br /&gt;
Um sich den Algorithmus der ''binären Suche'' klar zu machen, ist es instruktiv, sich die folgende Tabelle genauer anzusehen, die die sukzessive Belegung der Variablen bei verschiedenen Anfragen beschreibt. Die Testfälle wurden nach dem Prinzip des ''domain partitioning'' gewählt. Das zugehörige Array hat die Einträge&lt;br /&gt;
&lt;br /&gt;
 a = [2, 3, 4, 5, 6]&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align:center&amp;quot; border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;5&amp;quot; cellspacing=&amp;quot;0&amp;quot; &lt;br /&gt;
! gesuchter key   !!  start      !! end  !! size !! center !! return &amp;lt;br/&amp;gt; (-1 oder index)  !! Kommentare  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 4     ||0            || 5    ||  5   || 2   ||  2         || gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 2     || 0           || 5    ||  5   || 2   ||            || linker Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||           &lt;br /&gt;
|-&lt;br /&gt;
|       ||  0          || 1    ||  1   || 0   ||  0         || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 1     ||0            || 5    ||  5   || 2   ||            || links außerhalb&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 1    ||  1   || 0   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 0    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 6     ||0            || 5    ||  5   || 2   ||            || rechter Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    || 2    || 4   || 4          || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 5     ||0            || 5    ||  5   || 2   ||            || typischer Fall&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||3            || 5    ||  2   || 4   ||            ||  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       || 3           || 4    ||  1   || 3   || 3          || gefunden&lt;br /&gt;
|- &lt;br /&gt;
| 7     ||0            || 5    ||  5   || 2   ||            || rechts außerhalb        &lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    ||  2   || 4   ||            ||&lt;br /&gt;
|-&lt;br /&gt;
|       ||5            || 5    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Suche in einem Binärbaum ==&lt;br /&gt;
&lt;br /&gt;
Eine kurze Einführung in Binärbäume findet man [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum hier].&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum.png|text-top|300x300px|Zur Illustration von Bäumen]]&lt;br /&gt;
&lt;br /&gt;
Bäume sind zweidimensional verkettete Strukturen. Sie gehören zu den fundamentalen Datenstrukturen in der Informatik. Da man in Bäumen nicht nur Daten speichern kann, sondern auch relevante Beziehungen der Daten untereinander, festgelegt über eine Ordnung auf der vergleichenden Dateneigenschaft (''Schlüssel''), eignen sich Bäume also insbesondere, um gesuchte Daten schnell wieder auffinden zu können.&lt;br /&gt;
&lt;br /&gt;
Ein ''Binärbaum'' wie oben skizziert besteht aus einer Menge von ''Knoten'', die untereinander durch ''Kanten'' verbunden sind. Jeder Knoten hat einen linken und einen rechten Unterbaum, der auch leer sein kann (in Python ließe sich dies mit ''None'' implementieren). Führt eine Kante von Knoten A zu Knoten B, so heißt A Vater von B und B Kind von A. Es gibt genau einen Knoten ohne Vater, den man ''Wurzel'' nennt. Knoten ohne Kinder heißen ''Blätter''.&lt;br /&gt;
&lt;br /&gt;
Ein ''Suchbaum'' hat zusätzlich die Eigenschaft, dass die Schlüssel jedes Knotens sortiert sind. Alle Schlüssel im linken Unterbaum sind kleiner, alle Schlüssel im rechten Unterbaum sind größer als ihr Vater. Wir wollen hierbei annehmen, dass jeder Schlüssel pro Datensatz nur einmal vorkommt, da sich sonst die &amp;gt;- oder &amp;lt;-Relation nicht mehr strikt erfüllen ließe.&lt;br /&gt;
&lt;br /&gt;
Um in einem Baum suchen zu können, wollen wir von zwei Annahmen ausgehen:&lt;br /&gt;
# Einfügen und Suchen im Baum wechseln sich ab. (Wenn das Suchen erst beginnt, nachdem alle Einfügungen erfolgt sind, wäre ein dynamisches Array mit binärer Suche wie oben wesentlich einfacher.)&lt;br /&gt;
# Der Schlüssel, der die Anordnung bestimmt, kennt eine [http://de.wikipedia.org/wiki/Ordnungsrelation Ordnung] (&amp;lt;-Relation oder &amp;gt;-Relation).&lt;br /&gt;
&lt;br /&gt;
Der folgende Python-Code zeigt beispielhaft, wie man in einem Suchbaum suchen könnte. Der Konstruktor für einen Knoten des Suchbaums ließe sich zum Beispiel so implementieren:&lt;br /&gt;
 &lt;br /&gt;
 class Node:&lt;br /&gt;
     def __init__(self, key):&lt;br /&gt;
         self.key = key&lt;br /&gt;
         self.left = self.right = None&lt;br /&gt;
 &lt;br /&gt;
 root = ...    # Wurzel des Suchbaums&lt;br /&gt;
 nodeFound = treeSearch(root, key)   # None, falls nichts gefunden&lt;br /&gt;
 &lt;br /&gt;
 def treeSearch(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return None&lt;br /&gt;
     elif node.key == key:&lt;br /&gt;
         return node&lt;br /&gt;
     elif key &amp;lt; node.key:&lt;br /&gt;
         return treeSearch(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         return treeSearch(node.right, key)&lt;br /&gt;
&lt;br /&gt;
Daraus resultiert der folgende Suchalgorithmus:&lt;br /&gt;
&lt;br /&gt;
 def treeSort(node,array):     # dynamisches Array als 2. Argument&lt;br /&gt;
     if node is None:     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
         return None:&lt;br /&gt;
     treeSort(node.left, array)     # rekursiv&lt;br /&gt;
     array.append(node.key)     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
     treeSort(node.right, array)     # rekursiv&lt;br /&gt;
&lt;br /&gt;
Komplexität:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 f(N)=\mathcal{O}(1)+f(N_\mathrm{left})+f(N_\mathrm{right})=\mathcal{O}(1)+\mathcal{O}(1)+f(N_\mathrm{leftleft})+f(N_\mathrm{leftright})+\mathcal{O}(1)+f(N_\mathrm{rightleft})&lt;br /&gt;
 +f(N_\mathrm{leftright})=N\ast\mathcal{O}(1)=\mathcal{O}(N)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sortier-Pseudocode:&lt;br /&gt;
&lt;br /&gt;
 Sortieren:&lt;br /&gt;
     (Array) a     # unsortiert&lt;br /&gt;
     (tree) t     # zunächst leer&lt;br /&gt;
 (dynamisches Array) r     # später sortiert&lt;br /&gt;
 for e in a:&lt;br /&gt;
     t = treeInsert(t, e)&lt;br /&gt;
 treeSort(t, r)&lt;br /&gt;
&lt;br /&gt;
== Insert ==&lt;br /&gt;
&lt;br /&gt;
Was passiert wenn der key (Schlüssel) schon vorhanden ist?&lt;br /&gt;
* error (exception)&lt;br /&gt;
* nichts einfügen&lt;br /&gt;
* nichts einfügen aber einen boolean return zurückgeben (einfügen=true, false)&lt;br /&gt;
* nochmals eingefügt (z.B. in der Node Klasse)&lt;br /&gt;
&lt;br /&gt;
Wobei die ersten 3 Punkte zur Mengensemantik gehören und der letzte eine Multimenge ist.&lt;br /&gt;
&lt;br /&gt;
Algorithmus im zweiten Fall (nichts einfügen):&lt;br /&gt;
&lt;br /&gt;
 def treeInsert(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return Node(key)     # Alternative Schreibweise: node = Node(key)&lt;br /&gt;
     if node.key == key:      # und dann:&lt;br /&gt;
         return node          # pass&lt;br /&gt;
     elif key &amp;lt; node.key:     # links im Baum&lt;br /&gt;
         node.left = treeInsert(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         node.right = treeInsert(node.right, key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Remove ==&lt;br /&gt;
Fälle:&lt;br /&gt;
# key (bzw. Knoten der key enthält) ist ein Blatt =&amp;gt; einfach löschen&lt;br /&gt;
# node hat &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; linken Unterbaum oder &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; rechten Unterbaum =&amp;gt; durch Unterbaum ersetzen&lt;br /&gt;
# node hat beide Unterbäume:&lt;br /&gt;
#* Suche Vorgänger: max k &amp;lt; key (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; keys); Vorgänger ist immer ein Fall 1 oder Fall 2&lt;br /&gt;
#*:=&amp;gt; ersetze node durch Vorgänger und entferne Vorgänger&lt;br /&gt;
&lt;br /&gt;
 def treePredecessor(node):     # wird nur bei Fall 3 aufgerufen&lt;br /&gt;
     node = node.left&lt;br /&gt;
     while node.right is not None:&lt;br /&gt;
         node = node.right&lt;br /&gt;
     return node&lt;br /&gt;
 &lt;br /&gt;
 def treeRemove(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return node&lt;br /&gt;
     if key &amp;lt; node.key:&lt;br /&gt;
         node.left = treeRemove(node.left, key)&lt;br /&gt;
     elif key &amp;gt; node.key:&lt;br /&gt;
         node.right = treeRemove(node.right, key)&lt;br /&gt;
     else:&lt;br /&gt;
         if node.left is None and node.right is None:     # Fall 1&lt;br /&gt;
             node = None&lt;br /&gt;
         elif node.left is None:     # Fall 2&lt;br /&gt;
             node = node.right       # +&lt;br /&gt;
         elif node.right is None:    # Fall 2&lt;br /&gt;
             node = node.left&lt;br /&gt;
         else:&lt;br /&gt;
             pred = treePredecessor(node)&lt;br /&gt;
             node.key = pred.key&lt;br /&gt;
             node.left = treeRemove(node.left, pred.key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Komplexitätsanalyse ==&lt;br /&gt;
&lt;br /&gt;
* Pfad (Zwischen node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;): &lt;br /&gt;
**Folge von Knoten (node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt;,...,node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt;), sodass:&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;ki&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;ki+1&amp;lt;/sub&amp;gt; haben eine gemeinsame Kante.&lt;br /&gt;
[[Image:Baum_Pfad.png]]&lt;br /&gt;
(Ein Baum ist ein Graph, indem es zwischen beliebigen Knoten stets genau einen Pfad gibt.)&lt;br /&gt;
&lt;br /&gt;
* Länge eines Pfades: Anzahl der Kanten = Anzahl der Knoten - 1&lt;br /&gt;
* Tiefe eines Knotens: Pfadlänge vom Knoten zur Wurzel des Baumes.&lt;br /&gt;
* Tiefe des Baumes: maximale Tiefe eines Knotens&lt;br /&gt;
* Balance eines Baumes: maximale Tiefe(k) - minimale Tiefe(k) (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; Blätter)&lt;br /&gt;
&lt;br /&gt;
== Ungünstigster Fall von treeSearch ==&lt;br /&gt;
&lt;br /&gt;
Komplexität von treeSearch = Länge des Pfades zum Knoten wo &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; gefunden wird, oder erkannt wird, dass &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; nicht im Baum ist.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; wird nicht gefunden, aber für diese Entscheidung muss der Längste Pfad vollständig durchlaufen werden.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(T)&amp;lt;/math&amp;gt; wo T = Tiefe des Baumes&lt;br /&gt;
*: [1,2,3,4,5]:&amp;lt;br /&amp;gt; [[Image:Balance.png]]&lt;br /&gt;
*: Tiefe T = 4, Balance = 4&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; wo N = Anzahl der Elemente.&lt;br /&gt;
&lt;br /&gt;
== Aufgabe ==&lt;br /&gt;
&lt;br /&gt;
Minimiere Balance (erzeuge balancierten Baum):&lt;br /&gt;
# Einfügen in geschickter Reihenfolge (siehe Übungsaufgabe)&lt;br /&gt;
# Selbstbalancierter Baum:&lt;br /&gt;
#* Überprüfen der Balance nach jedem Einfügen&lt;br /&gt;
#* Umstrukturieren des Baumes, falls Balance &amp;gt; 1 (Suchbaum-Bedingung muss erhalten bleiben)&lt;br /&gt;
#* AVL-Bäume (älteste Variante)&lt;br /&gt;
#* Rot-Schwarz-Bäume (verbreiteste Variante)&lt;br /&gt;
#* Treaps (flexibelste Variante, siehe Übung)&lt;br /&gt;
#* Splay trees&lt;br /&gt;
#* Andersson Trees (einfachste Variante)&lt;br /&gt;
(#* Skip Lists (schnellste Variante, aber kein Binärbaum))&lt;br /&gt;
&lt;br /&gt;
== Umstrukturieren, so dass Suchbaumbedingung erhalten bleibt: ==&lt;br /&gt;
&lt;br /&gt;
Rotation: elementare Umstrukturierungen&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum_Rotation.png]]&lt;br /&gt;
&lt;br /&gt;
 def rotateRight(node):&lt;br /&gt;
     newRoot = node.left&lt;br /&gt;
     node.left = newRoot.right&lt;br /&gt;
     newRoot.right = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
 def rotateLeft(node):&lt;br /&gt;
     newRoot = node.right&lt;br /&gt;
     node.right = newRoot.left&lt;br /&gt;
     newRoot.left = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
== Balance eines Suchbaumes ==&lt;br /&gt;
&lt;br /&gt;
===Balance eines Baumes zu  definieren:===&lt;br /&gt;
[[Image:Abbildung1.jpg|250px|right]]&lt;br /&gt;
*betrachte &amp;lt;tt&amp;gt;None&amp;lt;/tt&amp;gt; als zusätzlichen Knoten : '''sentinel'''   ([[englische Sprache|engl.]] für ''Wächter'')&lt;br /&gt;
*oder defeniere speziellen sentinel-Knoten&lt;br /&gt;
*RS - Pfades : von &amp;lt;tt&amp;gt;root&amp;lt;/tt&amp;gt; &amp;amp;rarr; ''sentinel''&lt;br /&gt;
*Länge eines Pfades ''P''      '''''|P|'''''&lt;br /&gt;
* &amp;lt;u&amp;gt;die Balance eines Baumes zu defenieren:&amp;lt;/u&amp;gt;&lt;br /&gt;
: '''''B = max P '''''&amp;lt;math&amp;gt; \in &amp;lt;/math&amp;gt; '''''{RS}    |P| - min |P|'''''&lt;br /&gt;
:'''''{RS}'''''  Menge aller RS-Pfade&lt;br /&gt;
;vollständiger Baum:  Balance &amp;lt;math&amp;gt;~=0&amp;lt;/math&amp;gt;&lt;br /&gt;
:Alle Knoten(außer Blättern) haben 2 Kinder.&lt;br /&gt;
;perfect balancierter Baum:  Balance  &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 1&lt;br /&gt;
'''alternative Defenition für perfect balancierte Bäume'''&lt;br /&gt;
:Für jeden Teilbaum gilt es: rechtes und linkes Kind (für jeden Knoten) sind auch wieder perfect balancierte Bäume und ihre Höhe unterscheidet sich höchstens um '''1'''.&lt;br /&gt;
&lt;br /&gt;
===Größe===&lt;br /&gt;
[[Image:baum-v.png|700px|right]]&lt;br /&gt;
====Größe des vollständigen Baumes====&lt;br /&gt;
Ebene K hat 2 &amp;lt;sup&amp;gt;k&amp;lt;/sup&amp;gt; Knoten.&lt;br /&gt;
Falls Tiefe = d , dann N = 2&amp;lt;sup&amp;gt;0&amp;lt;/sup&amp;gt; + 2&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;.....+ 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; = 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1&lt;br /&gt;
&lt;br /&gt;
====Größe des perfect balancierten Baumes====&lt;br /&gt;
Die Tiefe d, kann nicht besser sein als vollständiger Baum&lt;br /&gt;
:'''=&amp;gt;'''N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2 &amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
;schlechteste perfect balancierter Baum : ist ein vollständiger der Tiefe  d - 1 + 1, N &amp;lt;math&amp;gt; \ge&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:'''=&amp;gt;'''  2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1)   &amp;lt;math&amp;gt;~&amp;lt;&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt;)&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
d &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt;  log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; (d + 1)&lt;br /&gt;
&lt;br /&gt;
*Ergibt die Komplexität der Suche im schlechtesten Fall: Anzahl der Vergleiche pro Knoten( = 2 bzw. = 3)&amp;lt;math&amp;gt;\ast&amp;lt;/math&amp;gt;Anzahl der Knoten&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow ~f(N)\le 2d \le 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
*'''perfekt balancierter Baum '''=&amp;gt; AVL-Baum &lt;br /&gt;
*'''balancierter Baum''':&lt;br /&gt;
::&amp;lt;math&amp;gt;\forall  N:d(N)\le c \ast d (N)&amp;lt;/math&amp;gt; und  &amp;lt;math&amp;gt;1 \le c &amp;lt; \mathcal {1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:: d ist die Tiefe von perfekt balancierten Baum&lt;br /&gt;
&lt;br /&gt;
:''Komplexität der Suche'':&amp;lt;math&amp;gt; ~f(N)\le  c\ast 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
:algorithmisch einfacher als perfekt balancierter Baum, aber fast genauso schnell&lt;br /&gt;
&lt;br /&gt;
====Selbst-balancierter Baum====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  def insertTree(node,key):&lt;br /&gt;
    if node is sentinel:       #(None = sentinel)&lt;br /&gt;
        return Node(key)       &lt;br /&gt;
    if node.key == key:&lt;br /&gt;
        return Node&lt;br /&gt;
    if key &amp;lt; node.key:&lt;br /&gt;
        Node = insertTree(node.left, key)&lt;br /&gt;
    else:&lt;br /&gt;
        node.right = insertTree(node.right, key)&lt;br /&gt;
   #optimiere Balance:&lt;br /&gt;
        return node&lt;br /&gt;
&lt;br /&gt;
===Anderson-Bäume===&lt;br /&gt;
&lt;br /&gt;
  class Node:&lt;br /&gt;
    def__init__(self, key):&lt;br /&gt;
        self.key = key&lt;br /&gt;
        self.left = selft.right = None&lt;br /&gt;
        #einfügen:&lt;br /&gt;
        self.level = 1&lt;br /&gt;
&lt;br /&gt;
:''level'' : kodiert Abstand von sentinel&lt;br /&gt;
====Regeln====&lt;br /&gt;
Es gibt vertikale Kanten(parent.level == child.level + 1 ) und horizontale Kanten(parent.level == child.level)&lt;br /&gt;
:*verfeinerter Regel :  ''level kodiert reduzierten Abstand von Setinel''  (d.b. horizontale Kanten werden nicht gezählt)&lt;br /&gt;
::(die nächste zwei Regeln sichern die Balance):&lt;br /&gt;
::*alle RS-Pfade haben die gleiche reduzierte Länge oder ''root'' hat bei allen Pfaden das gleiche Level&lt;br /&gt;
::*kein Pfad hat 2 aufeinanderer folgende horizontale Kanten&lt;br /&gt;
:*nur Kanten zum rechten Kind dürfen horizontal sein&lt;br /&gt;
:*die reduzierte Höhe jedes Blatts ist '''hr=1'''&lt;br /&gt;
&lt;br /&gt;
====Beweis====&lt;br /&gt;
Vereinfachung des Algorithmus&lt;br /&gt;
:'''Satz''':ein Anderson-Baum ist balanciert.&lt;br /&gt;
*1. Sei '''hr'''- die reduzierte Höhe&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; jeder Teilbaum enthält mindestens &amp;lt;math&amp;gt;N\ge 2^{hr} -1 &amp;lt;/math&amp;gt;  Knoten&lt;br /&gt;
&lt;br /&gt;
::a). Blätter : reduzierte Höhe 1 =&amp;gt; &amp;lt;math&amp;gt; N\ge 2^{1} - 1 = 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
::b). inneren Knoten: jeder Unterbaum hat mindestens reduzierte Höhe &amp;lt;math&amp;gt; ~hr - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt;  jeder Unterbaum hat mindestens &amp;lt;math&amp;gt; ~2^{hr} -1 &amp;lt;/math&amp;gt; Knoten&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt;N \ge 2 (2^{hr-1} -1)+1 = 2^w - 2 + 1 = 2^{hr} -1&amp;lt;/math&amp;gt;&lt;br /&gt;
::::alle RS-Pfade heben gleiche Länge&lt;br /&gt;
[[Image:Anderson.png|700px|right]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
*2. Kein Pfad hat 2 aufeinanderfolgende horizontal Kanten&lt;br /&gt;
:::&amp;lt;math&amp;gt;\Rightarrow  d \le 2 hr + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
*3. zusammen:&lt;br /&gt;
:&amp;lt;math&amp;gt;N \ge 2^{\frac {d}{2}} - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;log_{2}{ N}+1\ge log 2^{\frac {d}{2}} - 1 \ge \frac {d}{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; ~d &amp;lt; 2 log_{2}{(N + 1)}&amp;lt;/math&amp;gt;     - balancierter Baum&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
:'''Suchzeit''' : &amp;lt;math&amp;gt; ~ f(N) = \mathcal{O}(log{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Wie erreicht man die Balance===&lt;br /&gt;
====Rotation====&lt;br /&gt;
[[Image:Abbildung4.jpg|380px|Illustration von Rotation]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
   def rotateRight (node):&lt;br /&gt;
      root = node.left&lt;br /&gt;
      node.left = node.right&lt;br /&gt;
      root.right = node&lt;br /&gt;
      return root&lt;br /&gt;
   def rotateLeft(node):&lt;br /&gt;
      root = node.right&lt;br /&gt;
      node.right = node.left&lt;br /&gt;
      root.left = node&lt;br /&gt;
      return root&lt;br /&gt;
&lt;br /&gt;
====Optimierung der Balance====&lt;br /&gt;
   &lt;br /&gt;
&lt;br /&gt;
       if node.left is not sentinel and node.level==node.left.level:&lt;br /&gt;
            node &amp;gt; rotateRight(node)&lt;br /&gt;
       if node.right is not sentinel and node.right.right is  not sentinel and node.level==rotate.right.right.level:&lt;br /&gt;
            node = rotateLeft(node)&lt;br /&gt;
            node.level += 1&lt;/div&gt;</summary>
		<author><name>Nata</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1486</id>
		<title>Suchen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1486"/>
				<updated>2008-05-29T00:13:32Z</updated>
		
		<summary type="html">&lt;p&gt;Nata: /* Rotation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Es wäre super wenn jemand ganz kurz schreiben könnte, was am Donnerstag zu den Funktionen treeInsert/Remove/HasKey gemacht wurde, was man ja für den aktuellen Übungszettel braucht. Danke :-)&amp;lt;br /&amp;gt;&lt;br /&gt;
/edit: an eine Funktion &amp;quot;HasKey&amp;quot; kann ich mich aus der Vorlesung nicht erinnern. Wenn die jemand hat, bitte eintragen. - Protokollant&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--Hallo, ich kenne leider deinen Namen nicht, ich habe den Wiki-Eintrag von letztem Donnerstag gemacht.Da das Thema 'Dynamisches Array' noch Thema von letzter Woche war, hab ich die Wiederholung der amortisierten Kosten von diesem Mittwoch noch bei Effizienz eingetragen, du musst es also nicht noch mal in deinem Wiki eintragen. Du kannst dir aber gerne mal anschauen was ich geschrieben habe, vielleicht fallen dir noch ein paar Sachen ein, die man hätte besser machen können. lg, Franziska.--&amp;gt;&lt;br /&gt;
Das Suchen ist eine grundlegende Operation in der Informatik. Viele Probleme in der Informatik können auf Suchaufgaben zurückgeführt werden.&lt;br /&gt;
&lt;br /&gt;
Gemeint ist mit Suchen das Wiederauffinden einer oder mehrerer Datensätze aus einer Menge von früher gespeicherten Datensätzen. Ein paar einleitende Worte zum Suchproblem findet man [http://de.wikipedia.org/wiki/Suche hier].&lt;br /&gt;
&lt;br /&gt;
== Überblick verschiedener Suchmethoden ==&lt;br /&gt;
&lt;br /&gt;
Um sich der Vielseitigkeit von Suchproblemen bewusst zu werden, ist es sinnvoll, sich einen Überblick über verschiedene Suchmethoden zu verschaffen. &lt;br /&gt;
&lt;br /&gt;
Hier sei auch auf einen bereits existierenden Wikipedia-Artikel zu [http://de.wikipedia.org/wiki/Suchverfahren Suchverfahren] verwiesen.&lt;br /&gt;
&lt;br /&gt;
Allen gemeinsam ist die grundlegende Aufgabe, ein Datenelement mit bestimmten Eigenschaften aus einer großen Menge von Datenelementen zu selektieren.&lt;br /&gt;
Dies kann, natürlich ohne jeden Anspruch auf Vollständigkeit, nach einer der jetzt diskutierten Methoden geschehen:&lt;br /&gt;
&lt;br /&gt;
* '''Schlüsselsuche''': meint das Suchen von Elementen mit bestimmtem Schlüssel; ein klassisches Beispiel wäre das Suchen in einem Wörterbuch,  die Schlüssel entsprechen hier den Wörtern, die Datensätze wären die zu den Wörtern gehörigen Eintragungen.&lt;br /&gt;
&lt;br /&gt;
* '''Bereichssuche''': Im Allgemeinen meint die Bereichssuche in n-Dimensionen die Selektion von Elementen mit Eigenschaften aus einem bestimmten n-dimensionalen Volumen. Im eindimensionalen Fall will man alle Elemente finden, deren Eigenschaft(en) in einem bestimmten Intervall liegen. Die Verallgemeinerung auf n-Dimensionen ist offensichtlich. Ein Beispiel für die Bereichssuche in einer 3D-Kugel wäre ein Handy mit Geolokalisierung, welches alle Restaurants in einem Umkreis von 500m findet. Lineare Ungleichungen werden graphisch durch [http://de.wikipedia.org/wiki/Hyperebene Hyperebenen] repräsentiert. In 2D sind diese Hyperebenen Geraden. Die Ungleichungen können dann den Lösungsraum in irgendeiner Form begrenzen.&lt;br /&gt;
&lt;br /&gt;
* '''Ähnlichkeitssuche''': Finde Elemente, die gegebenen Eigenschaften möglichst ähnlich sind. Ein prominentes Beispiel ist Google (=Ähnlichkeit zwischen Suchbegriffen und Dokumenten) oder das Suchen des nächstengelegenen Restaurants (Ähnlichkeit zwischen eigener Position und Position des Restaurants). Ein wichtigster Spezialfall ist die ''nächste-nachbar Suche''.&lt;br /&gt;
&lt;br /&gt;
* '''Graphensuche''': Hier wäre beispielsweise das Problem optimaler Wege zu nennen (Navigationssuche). Dieser Punkt wird später im Verlauf der Vorlesung noch einmal aufgegriffen werden.&lt;br /&gt;
&lt;br /&gt;
Im jetzt Folgenden wird nur noch die ''Schlüsselsuche'' betrachtet werden.&lt;br /&gt;
&lt;br /&gt;
==Sequentielle Suche==&lt;br /&gt;
&lt;br /&gt;
Die ''sequentielle'' oder ''lineare'' Suche ist die 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 = sequentialSearch(a, key) &lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq &amp;lt;/math&amp;gt; foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def sequentialSearch(a, key):&lt;br /&gt;
    for i in range(len(a)):&lt;br /&gt;
        if a[i] == key:  # bzw. allgemeiner a[i].key == key &lt;br /&gt;
            return i&lt;br /&gt;
    return -1&lt;br /&gt;
&lt;br /&gt;
Wir wollen jetzt die Komplexität dieses Algorithmus bestimmen, wobei die Problemgröße durch &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt; gegeben ist. &lt;br /&gt;
&lt;br /&gt;
Dabei nimmt man an, dass der innerste Vergleich (a[i] == key) jeweils &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt; ist (diese Annahme könnte verletzt sein, wenn der Vergleichsoperator überladen ist und dadurch eine höhere Komplexität hat). Dieser Vergleich wird in der for-Schleife jeweils N-mal durchgeführt (&amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt;), so dass man nach der Verschachtelungsregel eine gesamte Komplexität von &amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt; erhält.&lt;br /&gt;
&lt;br /&gt;
Der Name ''lineare'' Suche rührt von diesem linearen Anwachsen der Komplexität mit der Arraygröße her.&lt;br /&gt;
&lt;br /&gt;
==Binäre Suche==&lt;br /&gt;
&lt;br /&gt;
Wie wir weiter unten zeigen werden, gestattet es diese Suchmethode, die Gesamtdauer der Suche in großen Datensätzen beträchtlich zu verringern. Die Methode beruht auf dem [http://de.wikipedia.org/wiki/Divide_and_Conquer Divide and Conquer-Prinzip], wobei die Suche in jedem Schritt rekursiv auf eine Hälfte des Datensatzes eingeschränkt wird. Weitere Details zur Methode sind [http://de.wikipedia.org/wiki/Bin%C3%A4re_Suche hier] zu finden. &lt;br /&gt;
&lt;br /&gt;
Die Methode ist nur dann anwendbar beziehungsweise effektiv, wenn folgendes gilt:&lt;br /&gt;
&lt;br /&gt;
# Auf der Eigenschaft der Daten, die zur Suche verwendet wird, ist eine Ordnung im Sinne von &amp;lt; oder &amp;gt; definiert.&lt;br /&gt;
# Wir wollen uns auf Datensätze beschränken, die schon fertig aufgebaut sind, in die also keine neuen Elemente mehr eingefügt werden, wenn man mit dem Suchen beginnt. Ist dies nicht der Fall, so ist unter Umständen die Implementierung über einen [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum Binärbaum] (siehe auch weiter unten) geschickter. &lt;br /&gt;
&lt;br /&gt;
Der folgende Algorithmus zeigt eine beispielhafte Implementierung der Methode (wir setzen wieder &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt;):&lt;br /&gt;
&lt;br /&gt;
 a = [...,...]     # array&lt;br /&gt;
 a.sort()   # sortiere über Ordnung des Schlüssels&lt;br /&gt;
 foundIndex = binSearch(a, key, 0, len(a))  # (Array, Schlüssel, von wo bis wo suchen im Array)&lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq&amp;lt;/math&amp;gt;  foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def binSearch(a, key, start, end):  # start ist 1. Index, end ist letzter Index + 1&lt;br /&gt;
    size = end - start   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if size &amp;lt;= 0:   # Bereich leer?  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return -1   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    center = (start + end)/2   # Integer Division, Ergebnis wird abgerundet, wichtig für ganzzahlige Indizes &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if a[center] == key:  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return center  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    elif a[center] &amp;lt; key:  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return binSearch(a, key, center + 1, end)  # Rekursion&lt;br /&gt;
    else:&lt;br /&gt;
        return binSearch(a, key, start + 1, center)  # Rekursion&lt;br /&gt;
&lt;br /&gt;
Zur Berechnung der Komplexität dieses Algorithmus vernachlässigen wir zunächst den Aufwand, den die Sortierung weiter oben verursacht. Dieser Schritt mag oder mag nicht zulässig sein.&lt;br /&gt;
&lt;br /&gt;
Nach der Sequenzregel haben auch alle &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;.  Es bleibt die Komplexität der Rekursion zu berechnen. Die gesamte Komplexität des Algorithmus (jetzt als Funktion f bezeichnet) setzt sich zusammen aus den oben erwähnten &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen sowie der Rekursion und ist &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f(N) = \mathcal{O}(1) + f(N/2) = \mathcal{O}(1) + \mathcal{O}(1) + f(N/4) = ... = \underbrace{\mathcal{O}(1) + ... + \mathcal{O}(1) + \underbrace{f(0)}_{\mathcal{O}(1)\, \rightarrow \,\mathrm{size-Abfrage}}}_{n+1 \,\mathrm{Terme}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Falls jetzt gilt &amp;lt;math&amp;gt; N = 2^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \rightarrow f(N) = \mathcal{O}(1) \cdot \mathcal{O}(n+1) = \mathcal{O}(n) = \mathcal{O}(\lg N) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Für große Datenmengen ist die ''binäre Suche'' also weit effizienter als die ''lineare Suche''. Verdoppelt sich beispielsweise die zu durchsuchende Datenmenge, so verdoppelt sich der Aufwand für die ''sequentielle Suche'' - bei der ''binären Suche'' hingegen benötigt man lediglich eine zusätzliche Vergleichsoperation. &lt;br /&gt;
&lt;br /&gt;
Für kleine Daten (&amp;lt;math&amp;gt; N = 4,\, 5 &amp;lt;/math&amp;gt;) ist die ''sequentielle Suche'' jedoch schneller als die ''binäre Suche'', da hier die rekursiven Funktionsaufrufe teurer als das Mehr an Vergleichen sind. Ein anderer ungünstiger Fall ist gegeben, wenn nur sehr wenige Suchanfragen erfolgen (weniger als &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; viele). Dann wird der Aufwand durch das Sortieren des Arrays dominiert, ist also &amp;lt;math&amp;gt;\mathcal{O}(N \lg N) &amp;lt;/math&amp;gt;. Auch dann ist sequentielle Suche vorzuziehen.&lt;br /&gt;
&lt;br /&gt;
Eine relativ einfache Möglichkeit, die ''binäre Suche'' zu verbessern, ist die sogenannte ''Interpolationssuche''. Hierbei wird die neue Position für die Suche, also die Mitte des Arrays, durch eine Schätzung ersetzt, die angibt, wo sich der Schlüssel innerhalb des Arrays befinden könnte. Bei der Suche in einem Telefonbuch nach dem Namen Zebra würde man ja auch nicht in der Mitte anfangen. Näheres hierzu im Buch von ''Sedgewick''.&lt;br /&gt;
&lt;br /&gt;
Um sich den Algorithmus der ''binären Suche'' klar zu machen, ist es instruktiv, sich die folgende Tabelle genauer anzusehen, die die sukzessive Belegung der Variablen bei verschiedenen Anfragen beschreibt. Die Testfälle wurden nach dem Prinzip des ''domain partitioning'' gewählt. Das zugehörige Array hat die Einträge&lt;br /&gt;
&lt;br /&gt;
 a = [2, 3, 4, 5, 6]&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align:center&amp;quot; border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;5&amp;quot; cellspacing=&amp;quot;0&amp;quot; &lt;br /&gt;
! gesuchter key   !!  start      !! end  !! size !! center !! return &amp;lt;br/&amp;gt; (-1 oder index)  !! Kommentare  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 4     ||0            || 5    ||  5   || 2   ||  2         || gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 2     || 0           || 5    ||  5   || 2   ||            || linker Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||           &lt;br /&gt;
|-&lt;br /&gt;
|       ||  0          || 1    ||  1   || 0   ||  0         || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 1     ||0            || 5    ||  5   || 2   ||            || links außerhalb&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 1    ||  1   || 0   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 0    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 6     ||0            || 5    ||  5   || 2   ||            || rechter Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    || 2    || 4   || 4          || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 5     ||0            || 5    ||  5   || 2   ||            || typischer Fall&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||3            || 5    ||  2   || 4   ||            ||  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       || 3           || 4    ||  1   || 3   || 3          || gefunden&lt;br /&gt;
|- &lt;br /&gt;
| 7     ||0            || 5    ||  5   || 2   ||            || rechts außerhalb        &lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    ||  2   || 4   ||            ||&lt;br /&gt;
|-&lt;br /&gt;
|       ||5            || 5    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Suche in einem Binärbaum ==&lt;br /&gt;
&lt;br /&gt;
Eine kurze Einführung in Binärbäume findet man [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum hier].&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum.png|text-top|300x300px|Zur Illustration von Bäumen]]&lt;br /&gt;
&lt;br /&gt;
Bäume sind zweidimensional verkettete Strukturen. Sie gehören zu den fundamentalen Datenstrukturen in der Informatik. Da man in Bäumen nicht nur Daten speichern kann, sondern auch relevante Beziehungen der Daten untereinander, festgelegt über eine Ordnung auf der vergleichenden Dateneigenschaft (''Schlüssel''), eignen sich Bäume also insbesondere, um gesuchte Daten schnell wieder auffinden zu können.&lt;br /&gt;
&lt;br /&gt;
Ein ''Binärbaum'' wie oben skizziert besteht aus einer Menge von ''Knoten'', die untereinander durch ''Kanten'' verbunden sind. Jeder Knoten hat einen linken und einen rechten Unterbaum, der auch leer sein kann (in Python ließe sich dies mit ''None'' implementieren). Führt eine Kante von Knoten A zu Knoten B, so heißt A Vater von B und B Kind von A. Es gibt genau einen Knoten ohne Vater, den man ''Wurzel'' nennt. Knoten ohne Kinder heißen ''Blätter''.&lt;br /&gt;
&lt;br /&gt;
Ein ''Suchbaum'' hat zusätzlich die Eigenschaft, dass die Schlüssel jedes Knotens sortiert sind. Alle Schlüssel im linken Unterbaum sind kleiner, alle Schlüssel im rechten Unterbaum sind größer als ihr Vater. Wir wollen hierbei annehmen, dass jeder Schlüssel pro Datensatz nur einmal vorkommt, da sich sonst die &amp;gt;- oder &amp;lt;-Relation nicht mehr strikt erfüllen ließe.&lt;br /&gt;
&lt;br /&gt;
Um in einem Baum suchen zu können, wollen wir von zwei Annahmen ausgehen:&lt;br /&gt;
# Einfügen und Suchen im Baum wechseln sich ab. (Wenn das Suchen erst beginnt, nachdem alle Einfügungen erfolgt sind, wäre ein dynamisches Array mit binärer Suche wie oben wesentlich einfacher.)&lt;br /&gt;
# Der Schlüssel, der die Anordnung bestimmt, kennt eine [http://de.wikipedia.org/wiki/Ordnungsrelation Ordnung] (&amp;lt;-Relation oder &amp;gt;-Relation).&lt;br /&gt;
&lt;br /&gt;
Der folgende Python-Code zeigt beispielhaft, wie man in einem Suchbaum suchen könnte. Der Konstruktor für einen Knoten des Suchbaums ließe sich zum Beispiel so implementieren:&lt;br /&gt;
 &lt;br /&gt;
 class Node:&lt;br /&gt;
     def __init__(self, key):&lt;br /&gt;
         self.key = key&lt;br /&gt;
         self.left = self.right = None&lt;br /&gt;
 &lt;br /&gt;
 root = ...    # Wurzel des Suchbaums&lt;br /&gt;
 nodeFound = treeSearch(root, key)   # None, falls nichts gefunden&lt;br /&gt;
 &lt;br /&gt;
 def treeSearch(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return None&lt;br /&gt;
     elif node.key == key:&lt;br /&gt;
         return node&lt;br /&gt;
     elif key &amp;lt; node.key:&lt;br /&gt;
         return treeSearch(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         return treeSearch(node.right, key)&lt;br /&gt;
&lt;br /&gt;
Daraus resultiert der folgende Suchalgorithmus:&lt;br /&gt;
&lt;br /&gt;
 def treeSort(node,array):     # dynamisches Array als 2. Argument&lt;br /&gt;
     if node is None:     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
         return None:&lt;br /&gt;
     treeSort(node.left, array)     # rekursiv&lt;br /&gt;
     array.append(node.key)     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
     treeSort(node.right, array)     # rekursiv&lt;br /&gt;
&lt;br /&gt;
Komplexität:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 f(N)=\mathcal{O}(1)+f(N_\mathrm{left})+f(N_\mathrm{right})=\mathcal{O}(1)+\mathcal{O}(1)+f(N_\mathrm{leftleft})+f(N_\mathrm{leftright})+\mathcal{O}(1)+f(N_\mathrm{rightleft})&lt;br /&gt;
 +f(N_\mathrm{leftright})=N\ast\mathcal{O}(1)=\mathcal{O}(N)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sortier-Pseudocode:&lt;br /&gt;
&lt;br /&gt;
 Sortieren:&lt;br /&gt;
     (Array) a     # unsortiert&lt;br /&gt;
     (tree) t     # zunächst leer&lt;br /&gt;
 (dynamisches Array) r     # später sortiert&lt;br /&gt;
 for e in a:&lt;br /&gt;
     t = treeInsert(t, e)&lt;br /&gt;
 treeSort(t, r)&lt;br /&gt;
&lt;br /&gt;
== Insert ==&lt;br /&gt;
&lt;br /&gt;
Was passiert wenn der key (Schlüssel) schon vorhanden ist?&lt;br /&gt;
* error (exception)&lt;br /&gt;
* nichts einfügen&lt;br /&gt;
* nichts einfügen aber einen boolean return zurückgeben (einfügen=true, false)&lt;br /&gt;
* nochmals eingefügt (z.B. in der Node Klasse)&lt;br /&gt;
&lt;br /&gt;
Wobei die ersten 3 Punkte zur Mengensemantik gehören und der letzte eine Multimenge ist.&lt;br /&gt;
&lt;br /&gt;
Algorithmus im zweiten Fall (nichts einfügen):&lt;br /&gt;
&lt;br /&gt;
 def treeInsert(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return Node(key)     # Alternative Schreibweise: node = Node(key)&lt;br /&gt;
     if node.key == key:      # und dann:&lt;br /&gt;
         return node          # pass&lt;br /&gt;
     elif key &amp;lt; node.key:     # links im Baum&lt;br /&gt;
         node.left = treeInsert(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         node.right = treeInsert(node.right, key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Remove ==&lt;br /&gt;
Fälle:&lt;br /&gt;
# key (bzw. Knoten der key enthält) ist ein Blatt =&amp;gt; einfach löschen&lt;br /&gt;
# node hat &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; linken Unterbaum oder &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; rechten Unterbaum =&amp;gt; durch Unterbaum ersetzen&lt;br /&gt;
# node hat beide Unterbäume:&lt;br /&gt;
#* Suche Vorgänger: max k &amp;lt; key (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; keys); Vorgänger ist immer ein Fall 1 oder Fall 2&lt;br /&gt;
#*:=&amp;gt; ersetze node durch Vorgänger und entferne Vorgänger&lt;br /&gt;
&lt;br /&gt;
 def treePredecessor(node):     # wird nur bei Fall 3 aufgerufen&lt;br /&gt;
     node = node.left&lt;br /&gt;
     while node.right is not None:&lt;br /&gt;
         node = node.right&lt;br /&gt;
     return node&lt;br /&gt;
 &lt;br /&gt;
 def treeRemove(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return node&lt;br /&gt;
     if key &amp;lt; node.key:&lt;br /&gt;
         node.left = treeRemove(node.left, key)&lt;br /&gt;
     elif key &amp;gt; node.key:&lt;br /&gt;
         node.right = treeRemove(node.right, key)&lt;br /&gt;
     else:&lt;br /&gt;
         if node.left is None and node.right is None:     # Fall 1&lt;br /&gt;
             node = None&lt;br /&gt;
         elif node.left is None:     # Fall 2&lt;br /&gt;
             node = node.right       # +&lt;br /&gt;
         elif node.right is None:    # Fall 2&lt;br /&gt;
             node = node.left&lt;br /&gt;
         else:&lt;br /&gt;
             pred = treePredecessor(node)&lt;br /&gt;
             node.key = pred.key&lt;br /&gt;
             node.left = treeRemove(node.left, pred.key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Komplexitätsanalyse ==&lt;br /&gt;
&lt;br /&gt;
* Pfad (Zwischen node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;): &lt;br /&gt;
**Folge von Knoten (node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt;,...,node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt;), sodass:&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;ki&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;ki+1&amp;lt;/sub&amp;gt; haben eine gemeinsame Kante.&lt;br /&gt;
[[Image:Baum_Pfad.png]]&lt;br /&gt;
(Ein Baum ist ein Graph, indem es zwischen beliebigen Knoten stets genau einen Pfad gibt.)&lt;br /&gt;
&lt;br /&gt;
* Länge eines Pfades: Anzahl der Kanten = Anzahl der Knoten - 1&lt;br /&gt;
* Tiefe eines Knotens: Pfadlänge vom Knoten zur Wurzel des Baumes.&lt;br /&gt;
* Tiefe des Baumes: maximale Tiefe eines Knotens&lt;br /&gt;
* Balance eines Baumes: maximale Tiefe(k) - minimale Tiefe(k) (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; Blätter)&lt;br /&gt;
&lt;br /&gt;
== Ungünstigster Fall von treeSearch ==&lt;br /&gt;
&lt;br /&gt;
Komplexität von treeSearch = Länge des Pfades zum Knoten wo &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; gefunden wird, oder erkannt wird, dass &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; nicht im Baum ist.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; wird nicht gefunden, aber für diese Entscheidung muss der Längste Pfad vollständig durchlaufen werden.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(T)&amp;lt;/math&amp;gt; wo T = Tiefe des Baumes&lt;br /&gt;
*: [1,2,3,4,5]:&amp;lt;br /&amp;gt; [[Image:Balance.png]]&lt;br /&gt;
*: Tiefe T = 4, Balance = 4&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; wo N = Anzahl der Elemente.&lt;br /&gt;
&lt;br /&gt;
== Aufgabe ==&lt;br /&gt;
&lt;br /&gt;
Minimiere Balance (erzeuge balancierten Baum):&lt;br /&gt;
# Einfügen in geschickter Reihenfolge (siehe Übungsaufgabe)&lt;br /&gt;
# Selbstbalancierter Baum:&lt;br /&gt;
#* Überprüfen der Balance nach jedem Einfügen&lt;br /&gt;
#* Umstrukturieren des Baumes, falls Balance &amp;gt; 1 (Suchbaum-Bedingung muss erhalten bleiben)&lt;br /&gt;
#* AVL-Bäume (älteste Variante)&lt;br /&gt;
#* Rot-Schwarz-Bäume (verbreiteste Variante)&lt;br /&gt;
#* Treaps (flexibelste Variante, siehe Übung)&lt;br /&gt;
#* Splay trees&lt;br /&gt;
#* Andersson Trees (einfachste Variante)&lt;br /&gt;
(#* Skip Lists (schnellste Variante, aber kein Binärbaum))&lt;br /&gt;
&lt;br /&gt;
== Umstrukturieren, so dass Suchbaumbedingung erhalten bleibt: ==&lt;br /&gt;
&lt;br /&gt;
Rotation: elementare Umstrukturierungen&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum_Rotation.png]]&lt;br /&gt;
&lt;br /&gt;
 def rotateRight(node):&lt;br /&gt;
     newRoot = node.left&lt;br /&gt;
     node.left = newRoot.right&lt;br /&gt;
     newRoot.right = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
 def rotateLeft(node):&lt;br /&gt;
     newRoot = node.right&lt;br /&gt;
     node.right = newRoot.left&lt;br /&gt;
     newRoot.left = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
== Balance eines Suchbaumes ==&lt;br /&gt;
&lt;br /&gt;
===Balance eines Baumes zu  definieren:===&lt;br /&gt;
[[Image:Abbildung1.jpg|250px|right]]&lt;br /&gt;
*betrachte &amp;lt;tt&amp;gt;None&amp;lt;/tt&amp;gt; als zusätzlichen Knoten : '''sentinel'''   ([[englische Sprache|engl.]] für ''Wächter'')&lt;br /&gt;
*oder defeniere speziellen sentinel-Knoten&lt;br /&gt;
*RS - Pfades : von &amp;lt;tt&amp;gt;root&amp;lt;/tt&amp;gt; &amp;amp;rarr; ''sentinel''&lt;br /&gt;
*Länge eines Pfades ''P''      '''''|P|'''''&lt;br /&gt;
* &amp;lt;u&amp;gt;die Balance eines Baumes zu defenieren:&amp;lt;/u&amp;gt;&lt;br /&gt;
: '''''B = max P '''''&amp;lt;math&amp;gt; \in &amp;lt;/math&amp;gt; '''''{RS}    |P| - min |P|'''''&lt;br /&gt;
:'''''{RS}'''''  Menge aller RS-Pfade&lt;br /&gt;
;vollständiger Baum:  Balance &amp;lt;math&amp;gt;~=0&amp;lt;/math&amp;gt;&lt;br /&gt;
:Alle Knoten(außer Blättern) haben 2 Kinder.&lt;br /&gt;
;perfect balancierter Baum:  Balance  &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 1&lt;br /&gt;
'''alternative Defenition für perfect balancierte Bäume'''&lt;br /&gt;
:Für jeden Teilbaum gilt es: rechtes und linkes Kind (für jeden Knoten) sind auch wieder perfect balancierte Bäume und ihre Höhe unterscheidet sich höchstens um '''1'''.&lt;br /&gt;
&lt;br /&gt;
===Größe===&lt;br /&gt;
[[Image:baum-v.png|700px|right]]&lt;br /&gt;
====Größe des vollständigen Baumes====&lt;br /&gt;
Ebene K hat 2 &amp;lt;sup&amp;gt;k&amp;lt;/sup&amp;gt; Knoten.&lt;br /&gt;
Falls Tiefe = d , dann N = 2&amp;lt;sup&amp;gt;0&amp;lt;/sup&amp;gt; + 2&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;.....+ 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; = 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1&lt;br /&gt;
&lt;br /&gt;
====Größe des perfect balancierten Baumes====&lt;br /&gt;
Die Tiefe d, kann nicht besser sein als vollständiger Baum&lt;br /&gt;
:'''=&amp;gt;'''N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2 &amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
;schlechteste perfect balancierter Baum : ist ein vollständiger der Tiefe  d - 1 + 1, N &amp;lt;math&amp;gt; \ge&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:'''=&amp;gt;'''  2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1)   &amp;lt;math&amp;gt;~&amp;lt;&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt;)&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
d &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt;  log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; (d + 1)&lt;br /&gt;
&lt;br /&gt;
*Ergibt die Komplexität der Suche im schlechtesten Fall: Anzahl der Vergleiche pro Knoten( = 2 bzw. = 3)&amp;lt;math&amp;gt;\ast&amp;lt;/math&amp;gt;Anzahl der Knoten&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow ~f(N)\le 2d \le 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
*'''perfekt balancierter Baum '''=&amp;gt; AVL-Baum &lt;br /&gt;
*'''balancierter Baum''':&lt;br /&gt;
::&amp;lt;math&amp;gt;\forall  N:d(N)\le c \ast d (N)&amp;lt;/math&amp;gt; und  &amp;lt;math&amp;gt;1 \le c &amp;lt; \mathcal {1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:: d ist die Tiefe von perfekt balancierten Baum&lt;br /&gt;
&lt;br /&gt;
:''Komplexität der Suche'':&amp;lt;math&amp;gt; ~f(N)\le  c\ast 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
:algorithmisch einfacher als perfekt balancierter Baum, aber fast genauso schnell&lt;br /&gt;
&lt;br /&gt;
====Selbst-balancierter Baum====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  def insertTree(node,key):&lt;br /&gt;
    if node is sentinel:       #(None = sentinel)&lt;br /&gt;
        return Node(key)       &lt;br /&gt;
    if node.key == key:&lt;br /&gt;
        return Node&lt;br /&gt;
    if key &amp;lt; node.key:&lt;br /&gt;
        Node = insertTree(node.left, key)&lt;br /&gt;
    else:&lt;br /&gt;
        node.right = insertTree(node.right, key)&lt;br /&gt;
   #optimiere Balance:&lt;br /&gt;
        return node&lt;br /&gt;
&lt;br /&gt;
===Anderson-Bäume===&lt;br /&gt;
&lt;br /&gt;
  class Node:&lt;br /&gt;
    def__init__(self, key):&lt;br /&gt;
        self.key = key&lt;br /&gt;
        self.left = selft.right = None&lt;br /&gt;
        #einfügen:&lt;br /&gt;
        self.level = 1&lt;br /&gt;
&lt;br /&gt;
:''level'' : kodiert Abstand von sentinel&lt;br /&gt;
====Regeln====&lt;br /&gt;
Es gibt vertikale Kanten(parent.level == child.level + 1 ) und horizontale Kanten(parent.level == child.level)&lt;br /&gt;
:*verfeinerter Regel :  ''level kodiert reduzierten Abstand von Setinel''  (d.b. horizontale Kanten werden nicht gezählt)&lt;br /&gt;
::(die nächste zwei Regeln sichern die Balance):&lt;br /&gt;
::*alle RS-Pfade haben die gleiche reduzierte Länge oder ''root'' hat bei allen Pfaden das gleiche Level&lt;br /&gt;
::*kein Pfad hat 2 aufeinanderer folgende horizontale Kanten&lt;br /&gt;
:*nur Kanten zum rechten Kind dürfen horizontal sein&lt;br /&gt;
:*die reduzierte Höhe jedes Blatts ist '''hr=1'''&lt;br /&gt;
&lt;br /&gt;
====Beweis====&lt;br /&gt;
Vereinfachung des Algorithmus&lt;br /&gt;
:'''Satz''':ein Anderson-Baum ist balanciert.&lt;br /&gt;
*1. Sei '''hr'''- die reduzierte Höhe&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; jeder Teilbaum enthält mindestens &amp;lt;math&amp;gt;N\ge 2^{hr} -1 &amp;lt;/math&amp;gt;  Knoten&lt;br /&gt;
&lt;br /&gt;
::a). Blätter : reduzierte Höhe 1 =&amp;gt; &amp;lt;math&amp;gt; N\ge 2^{1} - 1 = 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
::b). inneren Knoten: jeder Unterbaum hat mindestens reduzierte Höhe &amp;lt;math&amp;gt; ~hr - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt;  jeder Unterbaum hat mindestens &amp;lt;math&amp;gt; ~2^{hr} -1 &amp;lt;/math&amp;gt; Knoten&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt;N \ge 2 (2^{hr-1} -1)+1 = 2^w - 2 + 1 = 2^{hr} -1&amp;lt;/math&amp;gt;&lt;br /&gt;
::::alle RS-Pfade heben gleiche Länge&lt;br /&gt;
[[Image:Anderson.png|700px|right]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
*2. Kein Pfad hat 2 aufeinanderfolgende horizontal Kanten&lt;br /&gt;
:::&amp;lt;math&amp;gt;\Rightarrow  d \le 2 hr + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
*3. zusammen:&lt;br /&gt;
:&amp;lt;math&amp;gt;N \ge 2^{\frac {d}{2}} - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;log_{2}{ N}+1\ge log 2^{\frac {d}{2}} - 1 \ge \frac {d}{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; ~d &amp;lt; 2 log_{2}{(N + 1)}&amp;lt;/math&amp;gt;     - balancierter Baum&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
:'''Suchzeit''' : &amp;lt;math&amp;gt; ~ f(N) = \mathcal{O}(log{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Wie erreicht man die Balance===&lt;br /&gt;
====Rotation====&lt;br /&gt;
[[Image:Abbildung4.jpg|300px|Illustration von Rotation]]&lt;br /&gt;
   def rotateRight (node):&lt;br /&gt;
      root = node.left&lt;br /&gt;
      node.left = node.right&lt;br /&gt;
      root.right = node&lt;br /&gt;
      return root&lt;br /&gt;
   def rotateLeft(node):&lt;br /&gt;
      root = node.right&lt;br /&gt;
      node.right = node.left&lt;br /&gt;
      root.left = node&lt;br /&gt;
      return root&lt;br /&gt;
&lt;br /&gt;
====Optimierung der Balance====&lt;br /&gt;
   &lt;br /&gt;
&lt;br /&gt;
       if node.left is not sentinel and node.level==node.left.level:&lt;br /&gt;
            node &amp;gt; rotateRight(node)&lt;br /&gt;
       if node.right is not sentinel and node.right.right is  not sentinel and node.level==rotate.right.right.level:&lt;br /&gt;
            node = rotateLeft(node)&lt;br /&gt;
            node.level += 1&lt;/div&gt;</summary>
		<author><name>Nata</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1485</id>
		<title>Suchen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1485"/>
				<updated>2008-05-29T00:10:34Z</updated>
		
		<summary type="html">&lt;p&gt;Nata: /* Rotation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Es wäre super wenn jemand ganz kurz schreiben könnte, was am Donnerstag zu den Funktionen treeInsert/Remove/HasKey gemacht wurde, was man ja für den aktuellen Übungszettel braucht. Danke :-)&amp;lt;br /&amp;gt;&lt;br /&gt;
/edit: an eine Funktion &amp;quot;HasKey&amp;quot; kann ich mich aus der Vorlesung nicht erinnern. Wenn die jemand hat, bitte eintragen. - Protokollant&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--Hallo, ich kenne leider deinen Namen nicht, ich habe den Wiki-Eintrag von letztem Donnerstag gemacht.Da das Thema 'Dynamisches Array' noch Thema von letzter Woche war, hab ich die Wiederholung der amortisierten Kosten von diesem Mittwoch noch bei Effizienz eingetragen, du musst es also nicht noch mal in deinem Wiki eintragen. Du kannst dir aber gerne mal anschauen was ich geschrieben habe, vielleicht fallen dir noch ein paar Sachen ein, die man hätte besser machen können. lg, Franziska.--&amp;gt;&lt;br /&gt;
Das Suchen ist eine grundlegende Operation in der Informatik. Viele Probleme in der Informatik können auf Suchaufgaben zurückgeführt werden.&lt;br /&gt;
&lt;br /&gt;
Gemeint ist mit Suchen das Wiederauffinden einer oder mehrerer Datensätze aus einer Menge von früher gespeicherten Datensätzen. Ein paar einleitende Worte zum Suchproblem findet man [http://de.wikipedia.org/wiki/Suche hier].&lt;br /&gt;
&lt;br /&gt;
== Überblick verschiedener Suchmethoden ==&lt;br /&gt;
&lt;br /&gt;
Um sich der Vielseitigkeit von Suchproblemen bewusst zu werden, ist es sinnvoll, sich einen Überblick über verschiedene Suchmethoden zu verschaffen. &lt;br /&gt;
&lt;br /&gt;
Hier sei auch auf einen bereits existierenden Wikipedia-Artikel zu [http://de.wikipedia.org/wiki/Suchverfahren Suchverfahren] verwiesen.&lt;br /&gt;
&lt;br /&gt;
Allen gemeinsam ist die grundlegende Aufgabe, ein Datenelement mit bestimmten Eigenschaften aus einer großen Menge von Datenelementen zu selektieren.&lt;br /&gt;
Dies kann, natürlich ohne jeden Anspruch auf Vollständigkeit, nach einer der jetzt diskutierten Methoden geschehen:&lt;br /&gt;
&lt;br /&gt;
* '''Schlüsselsuche''': meint das Suchen von Elementen mit bestimmtem Schlüssel; ein klassisches Beispiel wäre das Suchen in einem Wörterbuch,  die Schlüssel entsprechen hier den Wörtern, die Datensätze wären die zu den Wörtern gehörigen Eintragungen.&lt;br /&gt;
&lt;br /&gt;
* '''Bereichssuche''': Im Allgemeinen meint die Bereichssuche in n-Dimensionen die Selektion von Elementen mit Eigenschaften aus einem bestimmten n-dimensionalen Volumen. Im eindimensionalen Fall will man alle Elemente finden, deren Eigenschaft(en) in einem bestimmten Intervall liegen. Die Verallgemeinerung auf n-Dimensionen ist offensichtlich. Ein Beispiel für die Bereichssuche in einer 3D-Kugel wäre ein Handy mit Geolokalisierung, welches alle Restaurants in einem Umkreis von 500m findet. Lineare Ungleichungen werden graphisch durch [http://de.wikipedia.org/wiki/Hyperebene Hyperebenen] repräsentiert. In 2D sind diese Hyperebenen Geraden. Die Ungleichungen können dann den Lösungsraum in irgendeiner Form begrenzen.&lt;br /&gt;
&lt;br /&gt;
* '''Ähnlichkeitssuche''': Finde Elemente, die gegebenen Eigenschaften möglichst ähnlich sind. Ein prominentes Beispiel ist Google (=Ähnlichkeit zwischen Suchbegriffen und Dokumenten) oder das Suchen des nächstengelegenen Restaurants (Ähnlichkeit zwischen eigener Position und Position des Restaurants). Ein wichtigster Spezialfall ist die ''nächste-nachbar Suche''.&lt;br /&gt;
&lt;br /&gt;
* '''Graphensuche''': Hier wäre beispielsweise das Problem optimaler Wege zu nennen (Navigationssuche). Dieser Punkt wird später im Verlauf der Vorlesung noch einmal aufgegriffen werden.&lt;br /&gt;
&lt;br /&gt;
Im jetzt Folgenden wird nur noch die ''Schlüsselsuche'' betrachtet werden.&lt;br /&gt;
&lt;br /&gt;
==Sequentielle Suche==&lt;br /&gt;
&lt;br /&gt;
Die ''sequentielle'' oder ''lineare'' Suche ist die 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 = sequentialSearch(a, key) &lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq &amp;lt;/math&amp;gt; foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def sequentialSearch(a, key):&lt;br /&gt;
    for i in range(len(a)):&lt;br /&gt;
        if a[i] == key:  # bzw. allgemeiner a[i].key == key &lt;br /&gt;
            return i&lt;br /&gt;
    return -1&lt;br /&gt;
&lt;br /&gt;
Wir wollen jetzt die Komplexität dieses Algorithmus bestimmen, wobei die Problemgröße durch &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt; gegeben ist. &lt;br /&gt;
&lt;br /&gt;
Dabei nimmt man an, dass der innerste Vergleich (a[i] == key) jeweils &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt; ist (diese Annahme könnte verletzt sein, wenn der Vergleichsoperator überladen ist und dadurch eine höhere Komplexität hat). Dieser Vergleich wird in der for-Schleife jeweils N-mal durchgeführt (&amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt;), so dass man nach der Verschachtelungsregel eine gesamte Komplexität von &amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt; erhält.&lt;br /&gt;
&lt;br /&gt;
Der Name ''lineare'' Suche rührt von diesem linearen Anwachsen der Komplexität mit der Arraygröße her.&lt;br /&gt;
&lt;br /&gt;
==Binäre Suche==&lt;br /&gt;
&lt;br /&gt;
Wie wir weiter unten zeigen werden, gestattet es diese Suchmethode, die Gesamtdauer der Suche in großen Datensätzen beträchtlich zu verringern. Die Methode beruht auf dem [http://de.wikipedia.org/wiki/Divide_and_Conquer Divide and Conquer-Prinzip], wobei die Suche in jedem Schritt rekursiv auf eine Hälfte des Datensatzes eingeschränkt wird. Weitere Details zur Methode sind [http://de.wikipedia.org/wiki/Bin%C3%A4re_Suche hier] zu finden. &lt;br /&gt;
&lt;br /&gt;
Die Methode ist nur dann anwendbar beziehungsweise effektiv, wenn folgendes gilt:&lt;br /&gt;
&lt;br /&gt;
# Auf der Eigenschaft der Daten, die zur Suche verwendet wird, ist eine Ordnung im Sinne von &amp;lt; oder &amp;gt; definiert.&lt;br /&gt;
# Wir wollen uns auf Datensätze beschränken, die schon fertig aufgebaut sind, in die also keine neuen Elemente mehr eingefügt werden, wenn man mit dem Suchen beginnt. Ist dies nicht der Fall, so ist unter Umständen die Implementierung über einen [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum Binärbaum] (siehe auch weiter unten) geschickter. &lt;br /&gt;
&lt;br /&gt;
Der folgende Algorithmus zeigt eine beispielhafte Implementierung der Methode (wir setzen wieder &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt;):&lt;br /&gt;
&lt;br /&gt;
 a = [...,...]     # array&lt;br /&gt;
 a.sort()   # sortiere über Ordnung des Schlüssels&lt;br /&gt;
 foundIndex = binSearch(a, key, 0, len(a))  # (Array, Schlüssel, von wo bis wo suchen im Array)&lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq&amp;lt;/math&amp;gt;  foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def binSearch(a, key, start, end):  # start ist 1. Index, end ist letzter Index + 1&lt;br /&gt;
    size = end - start   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if size &amp;lt;= 0:   # Bereich leer?  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return -1   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    center = (start + end)/2   # Integer Division, Ergebnis wird abgerundet, wichtig für ganzzahlige Indizes &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if a[center] == key:  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return center  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    elif a[center] &amp;lt; key:  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return binSearch(a, key, center + 1, end)  # Rekursion&lt;br /&gt;
    else:&lt;br /&gt;
        return binSearch(a, key, start + 1, center)  # Rekursion&lt;br /&gt;
&lt;br /&gt;
Zur Berechnung der Komplexität dieses Algorithmus vernachlässigen wir zunächst den Aufwand, den die Sortierung weiter oben verursacht. Dieser Schritt mag oder mag nicht zulässig sein.&lt;br /&gt;
&lt;br /&gt;
Nach der Sequenzregel haben auch alle &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;.  Es bleibt die Komplexität der Rekursion zu berechnen. Die gesamte Komplexität des Algorithmus (jetzt als Funktion f bezeichnet) setzt sich zusammen aus den oben erwähnten &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen sowie der Rekursion und ist &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f(N) = \mathcal{O}(1) + f(N/2) = \mathcal{O}(1) + \mathcal{O}(1) + f(N/4) = ... = \underbrace{\mathcal{O}(1) + ... + \mathcal{O}(1) + \underbrace{f(0)}_{\mathcal{O}(1)\, \rightarrow \,\mathrm{size-Abfrage}}}_{n+1 \,\mathrm{Terme}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Falls jetzt gilt &amp;lt;math&amp;gt; N = 2^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \rightarrow f(N) = \mathcal{O}(1) \cdot \mathcal{O}(n+1) = \mathcal{O}(n) = \mathcal{O}(\lg N) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Für große Datenmengen ist die ''binäre Suche'' also weit effizienter als die ''lineare Suche''. Verdoppelt sich beispielsweise die zu durchsuchende Datenmenge, so verdoppelt sich der Aufwand für die ''sequentielle Suche'' - bei der ''binären Suche'' hingegen benötigt man lediglich eine zusätzliche Vergleichsoperation. &lt;br /&gt;
&lt;br /&gt;
Für kleine Daten (&amp;lt;math&amp;gt; N = 4,\, 5 &amp;lt;/math&amp;gt;) ist die ''sequentielle Suche'' jedoch schneller als die ''binäre Suche'', da hier die rekursiven Funktionsaufrufe teurer als das Mehr an Vergleichen sind. Ein anderer ungünstiger Fall ist gegeben, wenn nur sehr wenige Suchanfragen erfolgen (weniger als &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; viele). Dann wird der Aufwand durch das Sortieren des Arrays dominiert, ist also &amp;lt;math&amp;gt;\mathcal{O}(N \lg N) &amp;lt;/math&amp;gt;. Auch dann ist sequentielle Suche vorzuziehen.&lt;br /&gt;
&lt;br /&gt;
Eine relativ einfache Möglichkeit, die ''binäre Suche'' zu verbessern, ist die sogenannte ''Interpolationssuche''. Hierbei wird die neue Position für die Suche, also die Mitte des Arrays, durch eine Schätzung ersetzt, die angibt, wo sich der Schlüssel innerhalb des Arrays befinden könnte. Bei der Suche in einem Telefonbuch nach dem Namen Zebra würde man ja auch nicht in der Mitte anfangen. Näheres hierzu im Buch von ''Sedgewick''.&lt;br /&gt;
&lt;br /&gt;
Um sich den Algorithmus der ''binären Suche'' klar zu machen, ist es instruktiv, sich die folgende Tabelle genauer anzusehen, die die sukzessive Belegung der Variablen bei verschiedenen Anfragen beschreibt. Die Testfälle wurden nach dem Prinzip des ''domain partitioning'' gewählt. Das zugehörige Array hat die Einträge&lt;br /&gt;
&lt;br /&gt;
 a = [2, 3, 4, 5, 6]&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align:center&amp;quot; border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;5&amp;quot; cellspacing=&amp;quot;0&amp;quot; &lt;br /&gt;
! gesuchter key   !!  start      !! end  !! size !! center !! return &amp;lt;br/&amp;gt; (-1 oder index)  !! Kommentare  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 4     ||0            || 5    ||  5   || 2   ||  2         || gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 2     || 0           || 5    ||  5   || 2   ||            || linker Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||           &lt;br /&gt;
|-&lt;br /&gt;
|       ||  0          || 1    ||  1   || 0   ||  0         || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 1     ||0            || 5    ||  5   || 2   ||            || links außerhalb&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 1    ||  1   || 0   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 0    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 6     ||0            || 5    ||  5   || 2   ||            || rechter Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    || 2    || 4   || 4          || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 5     ||0            || 5    ||  5   || 2   ||            || typischer Fall&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||3            || 5    ||  2   || 4   ||            ||  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       || 3           || 4    ||  1   || 3   || 3          || gefunden&lt;br /&gt;
|- &lt;br /&gt;
| 7     ||0            || 5    ||  5   || 2   ||            || rechts außerhalb        &lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    ||  2   || 4   ||            ||&lt;br /&gt;
|-&lt;br /&gt;
|       ||5            || 5    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Suche in einem Binärbaum ==&lt;br /&gt;
&lt;br /&gt;
Eine kurze Einführung in Binärbäume findet man [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum hier].&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum.png|text-top|300x300px|Zur Illustration von Bäumen]]&lt;br /&gt;
&lt;br /&gt;
Bäume sind zweidimensional verkettete Strukturen. Sie gehören zu den fundamentalen Datenstrukturen in der Informatik. Da man in Bäumen nicht nur Daten speichern kann, sondern auch relevante Beziehungen der Daten untereinander, festgelegt über eine Ordnung auf der vergleichenden Dateneigenschaft (''Schlüssel''), eignen sich Bäume also insbesondere, um gesuchte Daten schnell wieder auffinden zu können.&lt;br /&gt;
&lt;br /&gt;
Ein ''Binärbaum'' wie oben skizziert besteht aus einer Menge von ''Knoten'', die untereinander durch ''Kanten'' verbunden sind. Jeder Knoten hat einen linken und einen rechten Unterbaum, der auch leer sein kann (in Python ließe sich dies mit ''None'' implementieren). Führt eine Kante von Knoten A zu Knoten B, so heißt A Vater von B und B Kind von A. Es gibt genau einen Knoten ohne Vater, den man ''Wurzel'' nennt. Knoten ohne Kinder heißen ''Blätter''.&lt;br /&gt;
&lt;br /&gt;
Ein ''Suchbaum'' hat zusätzlich die Eigenschaft, dass die Schlüssel jedes Knotens sortiert sind. Alle Schlüssel im linken Unterbaum sind kleiner, alle Schlüssel im rechten Unterbaum sind größer als ihr Vater. Wir wollen hierbei annehmen, dass jeder Schlüssel pro Datensatz nur einmal vorkommt, da sich sonst die &amp;gt;- oder &amp;lt;-Relation nicht mehr strikt erfüllen ließe.&lt;br /&gt;
&lt;br /&gt;
Um in einem Baum suchen zu können, wollen wir von zwei Annahmen ausgehen:&lt;br /&gt;
# Einfügen und Suchen im Baum wechseln sich ab. (Wenn das Suchen erst beginnt, nachdem alle Einfügungen erfolgt sind, wäre ein dynamisches Array mit binärer Suche wie oben wesentlich einfacher.)&lt;br /&gt;
# Der Schlüssel, der die Anordnung bestimmt, kennt eine [http://de.wikipedia.org/wiki/Ordnungsrelation Ordnung] (&amp;lt;-Relation oder &amp;gt;-Relation).&lt;br /&gt;
&lt;br /&gt;
Der folgende Python-Code zeigt beispielhaft, wie man in einem Suchbaum suchen könnte. Der Konstruktor für einen Knoten des Suchbaums ließe sich zum Beispiel so implementieren:&lt;br /&gt;
 &lt;br /&gt;
 class Node:&lt;br /&gt;
     def __init__(self, key):&lt;br /&gt;
         self.key = key&lt;br /&gt;
         self.left = self.right = None&lt;br /&gt;
 &lt;br /&gt;
 root = ...    # Wurzel des Suchbaums&lt;br /&gt;
 nodeFound = treeSearch(root, key)   # None, falls nichts gefunden&lt;br /&gt;
 &lt;br /&gt;
 def treeSearch(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return None&lt;br /&gt;
     elif node.key == key:&lt;br /&gt;
         return node&lt;br /&gt;
     elif key &amp;lt; node.key:&lt;br /&gt;
         return treeSearch(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         return treeSearch(node.right, key)&lt;br /&gt;
&lt;br /&gt;
Daraus resultiert der folgende Suchalgorithmus:&lt;br /&gt;
&lt;br /&gt;
 def treeSort(node,array):     # dynamisches Array als 2. Argument&lt;br /&gt;
     if node is None:     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
         return None:&lt;br /&gt;
     treeSort(node.left, array)     # rekursiv&lt;br /&gt;
     array.append(node.key)     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
     treeSort(node.right, array)     # rekursiv&lt;br /&gt;
&lt;br /&gt;
Komplexität:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 f(N)=\mathcal{O}(1)+f(N_\mathrm{left})+f(N_\mathrm{right})=\mathcal{O}(1)+\mathcal{O}(1)+f(N_\mathrm{leftleft})+f(N_\mathrm{leftright})+\mathcal{O}(1)+f(N_\mathrm{rightleft})&lt;br /&gt;
 +f(N_\mathrm{leftright})=N\ast\mathcal{O}(1)=\mathcal{O}(N)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sortier-Pseudocode:&lt;br /&gt;
&lt;br /&gt;
 Sortieren:&lt;br /&gt;
     (Array) a     # unsortiert&lt;br /&gt;
     (tree) t     # zunächst leer&lt;br /&gt;
 (dynamisches Array) r     # später sortiert&lt;br /&gt;
 for e in a:&lt;br /&gt;
     t = treeInsert(t, e)&lt;br /&gt;
 treeSort(t, r)&lt;br /&gt;
&lt;br /&gt;
== Insert ==&lt;br /&gt;
&lt;br /&gt;
Was passiert wenn der key (Schlüssel) schon vorhanden ist?&lt;br /&gt;
* error (exception)&lt;br /&gt;
* nichts einfügen&lt;br /&gt;
* nichts einfügen aber einen boolean return zurückgeben (einfügen=true, false)&lt;br /&gt;
* nochmals eingefügt (z.B. in der Node Klasse)&lt;br /&gt;
&lt;br /&gt;
Wobei die ersten 3 Punkte zur Mengensemantik gehören und der letzte eine Multimenge ist.&lt;br /&gt;
&lt;br /&gt;
Algorithmus im zweiten Fall (nichts einfügen):&lt;br /&gt;
&lt;br /&gt;
 def treeInsert(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return Node(key)     # Alternative Schreibweise: node = Node(key)&lt;br /&gt;
     if node.key == key:      # und dann:&lt;br /&gt;
         return node          # pass&lt;br /&gt;
     elif key &amp;lt; node.key:     # links im Baum&lt;br /&gt;
         node.left = treeInsert(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         node.right = treeInsert(node.right, key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Remove ==&lt;br /&gt;
Fälle:&lt;br /&gt;
# key (bzw. Knoten der key enthält) ist ein Blatt =&amp;gt; einfach löschen&lt;br /&gt;
# node hat &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; linken Unterbaum oder &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; rechten Unterbaum =&amp;gt; durch Unterbaum ersetzen&lt;br /&gt;
# node hat beide Unterbäume:&lt;br /&gt;
#* Suche Vorgänger: max k &amp;lt; key (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; keys); Vorgänger ist immer ein Fall 1 oder Fall 2&lt;br /&gt;
#*:=&amp;gt; ersetze node durch Vorgänger und entferne Vorgänger&lt;br /&gt;
&lt;br /&gt;
 def treePredecessor(node):     # wird nur bei Fall 3 aufgerufen&lt;br /&gt;
     node = node.left&lt;br /&gt;
     while node.right is not None:&lt;br /&gt;
         node = node.right&lt;br /&gt;
     return node&lt;br /&gt;
 &lt;br /&gt;
 def treeRemove(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return node&lt;br /&gt;
     if key &amp;lt; node.key:&lt;br /&gt;
         node.left = treeRemove(node.left, key)&lt;br /&gt;
     elif key &amp;gt; node.key:&lt;br /&gt;
         node.right = treeRemove(node.right, key)&lt;br /&gt;
     else:&lt;br /&gt;
         if node.left is None and node.right is None:     # Fall 1&lt;br /&gt;
             node = None&lt;br /&gt;
         elif node.left is None:     # Fall 2&lt;br /&gt;
             node = node.right       # +&lt;br /&gt;
         elif node.right is None:    # Fall 2&lt;br /&gt;
             node = node.left&lt;br /&gt;
         else:&lt;br /&gt;
             pred = treePredecessor(node)&lt;br /&gt;
             node.key = pred.key&lt;br /&gt;
             node.left = treeRemove(node.left, pred.key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Komplexitätsanalyse ==&lt;br /&gt;
&lt;br /&gt;
* Pfad (Zwischen node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;): &lt;br /&gt;
**Folge von Knoten (node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt;,...,node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt;), sodass:&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;ki&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;ki+1&amp;lt;/sub&amp;gt; haben eine gemeinsame Kante.&lt;br /&gt;
[[Image:Baum_Pfad.png]]&lt;br /&gt;
(Ein Baum ist ein Graph, indem es zwischen beliebigen Knoten stets genau einen Pfad gibt.)&lt;br /&gt;
&lt;br /&gt;
* Länge eines Pfades: Anzahl der Kanten = Anzahl der Knoten - 1&lt;br /&gt;
* Tiefe eines Knotens: Pfadlänge vom Knoten zur Wurzel des Baumes.&lt;br /&gt;
* Tiefe des Baumes: maximale Tiefe eines Knotens&lt;br /&gt;
* Balance eines Baumes: maximale Tiefe(k) - minimale Tiefe(k) (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; Blätter)&lt;br /&gt;
&lt;br /&gt;
== Ungünstigster Fall von treeSearch ==&lt;br /&gt;
&lt;br /&gt;
Komplexität von treeSearch = Länge des Pfades zum Knoten wo &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; gefunden wird, oder erkannt wird, dass &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; nicht im Baum ist.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; wird nicht gefunden, aber für diese Entscheidung muss der Längste Pfad vollständig durchlaufen werden.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(T)&amp;lt;/math&amp;gt; wo T = Tiefe des Baumes&lt;br /&gt;
*: [1,2,3,4,5]:&amp;lt;br /&amp;gt; [[Image:Balance.png]]&lt;br /&gt;
*: Tiefe T = 4, Balance = 4&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; wo N = Anzahl der Elemente.&lt;br /&gt;
&lt;br /&gt;
== Aufgabe ==&lt;br /&gt;
&lt;br /&gt;
Minimiere Balance (erzeuge balancierten Baum):&lt;br /&gt;
# Einfügen in geschickter Reihenfolge (siehe Übungsaufgabe)&lt;br /&gt;
# Selbstbalancierter Baum:&lt;br /&gt;
#* Überprüfen der Balance nach jedem Einfügen&lt;br /&gt;
#* Umstrukturieren des Baumes, falls Balance &amp;gt; 1 (Suchbaum-Bedingung muss erhalten bleiben)&lt;br /&gt;
#* AVL-Bäume (älteste Variante)&lt;br /&gt;
#* Rot-Schwarz-Bäume (verbreiteste Variante)&lt;br /&gt;
#* Treaps (flexibelste Variante, siehe Übung)&lt;br /&gt;
#* Splay trees&lt;br /&gt;
#* Andersson Trees (einfachste Variante)&lt;br /&gt;
(#* Skip Lists (schnellste Variante, aber kein Binärbaum))&lt;br /&gt;
&lt;br /&gt;
== Umstrukturieren, so dass Suchbaumbedingung erhalten bleibt: ==&lt;br /&gt;
&lt;br /&gt;
Rotation: elementare Umstrukturierungen&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum_Rotation.png]]&lt;br /&gt;
&lt;br /&gt;
 def rotateRight(node):&lt;br /&gt;
     newRoot = node.left&lt;br /&gt;
     node.left = newRoot.right&lt;br /&gt;
     newRoot.right = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
 def rotateLeft(node):&lt;br /&gt;
     newRoot = node.right&lt;br /&gt;
     node.right = newRoot.left&lt;br /&gt;
     newRoot.left = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
== Balance eines Suchbaumes ==&lt;br /&gt;
&lt;br /&gt;
===Balance eines Baumes zu  definieren:===&lt;br /&gt;
[[Image:Abbildung1.jpg|250px|right]]&lt;br /&gt;
*betrachte &amp;lt;tt&amp;gt;None&amp;lt;/tt&amp;gt; als zusätzlichen Knoten : '''sentinel'''   ([[englische Sprache|engl.]] für ''Wächter'')&lt;br /&gt;
*oder defeniere speziellen sentinel-Knoten&lt;br /&gt;
*RS - Pfades : von &amp;lt;tt&amp;gt;root&amp;lt;/tt&amp;gt; &amp;amp;rarr; ''sentinel''&lt;br /&gt;
*Länge eines Pfades ''P''      '''''|P|'''''&lt;br /&gt;
* &amp;lt;u&amp;gt;die Balance eines Baumes zu defenieren:&amp;lt;/u&amp;gt;&lt;br /&gt;
: '''''B = max P '''''&amp;lt;math&amp;gt; \in &amp;lt;/math&amp;gt; '''''{RS}    |P| - min |P|'''''&lt;br /&gt;
:'''''{RS}'''''  Menge aller RS-Pfade&lt;br /&gt;
;vollständiger Baum:  Balance &amp;lt;math&amp;gt;~=0&amp;lt;/math&amp;gt;&lt;br /&gt;
:Alle Knoten(außer Blättern) haben 2 Kinder.&lt;br /&gt;
;perfect balancierter Baum:  Balance  &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 1&lt;br /&gt;
'''alternative Defenition für perfect balancierte Bäume'''&lt;br /&gt;
:Für jeden Teilbaum gilt es: rechtes und linkes Kind (für jeden Knoten) sind auch wieder perfect balancierte Bäume und ihre Höhe unterscheidet sich höchstens um '''1'''.&lt;br /&gt;
&lt;br /&gt;
===Größe===&lt;br /&gt;
[[Image:baum-v.png|700px|right]]&lt;br /&gt;
====Größe des vollständigen Baumes====&lt;br /&gt;
Ebene K hat 2 &amp;lt;sup&amp;gt;k&amp;lt;/sup&amp;gt; Knoten.&lt;br /&gt;
Falls Tiefe = d , dann N = 2&amp;lt;sup&amp;gt;0&amp;lt;/sup&amp;gt; + 2&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;.....+ 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; = 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1&lt;br /&gt;
&lt;br /&gt;
====Größe des perfect balancierten Baumes====&lt;br /&gt;
Die Tiefe d, kann nicht besser sein als vollständiger Baum&lt;br /&gt;
:'''=&amp;gt;'''N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2 &amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
;schlechteste perfect balancierter Baum : ist ein vollständiger der Tiefe  d - 1 + 1, N &amp;lt;math&amp;gt; \ge&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:'''=&amp;gt;'''  2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1)   &amp;lt;math&amp;gt;~&amp;lt;&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt;)&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
d &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt;  log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; (d + 1)&lt;br /&gt;
&lt;br /&gt;
*Ergibt die Komplexität der Suche im schlechtesten Fall: Anzahl der Vergleiche pro Knoten( = 2 bzw. = 3)&amp;lt;math&amp;gt;\ast&amp;lt;/math&amp;gt;Anzahl der Knoten&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow ~f(N)\le 2d \le 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
*'''perfekt balancierter Baum '''=&amp;gt; AVL-Baum &lt;br /&gt;
*'''balancierter Baum''':&lt;br /&gt;
::&amp;lt;math&amp;gt;\forall  N:d(N)\le c \ast d (N)&amp;lt;/math&amp;gt; und  &amp;lt;math&amp;gt;1 \le c &amp;lt; \mathcal {1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:: d ist die Tiefe von perfekt balancierten Baum&lt;br /&gt;
&lt;br /&gt;
:''Komplexität der Suche'':&amp;lt;math&amp;gt; ~f(N)\le  c\ast 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
:algorithmisch einfacher als perfekt balancierter Baum, aber fast genauso schnell&lt;br /&gt;
&lt;br /&gt;
====Selbst-balancierter Baum====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  def insertTree(node,key):&lt;br /&gt;
    if node is sentinel:       #(None = sentinel)&lt;br /&gt;
        return Node(key)       &lt;br /&gt;
    if node.key == key:&lt;br /&gt;
        return Node&lt;br /&gt;
    if key &amp;lt; node.key:&lt;br /&gt;
        Node = insertTree(node.left, key)&lt;br /&gt;
    else:&lt;br /&gt;
        node.right = insertTree(node.right, key)&lt;br /&gt;
   #optimiere Balance:&lt;br /&gt;
        return node&lt;br /&gt;
&lt;br /&gt;
===Anderson-Bäume===&lt;br /&gt;
&lt;br /&gt;
  class Node:&lt;br /&gt;
    def__init__(self, key):&lt;br /&gt;
        self.key = key&lt;br /&gt;
        self.left = selft.right = None&lt;br /&gt;
        #einfügen:&lt;br /&gt;
        self.level = 1&lt;br /&gt;
&lt;br /&gt;
:''level'' : kodiert Abstand von sentinel&lt;br /&gt;
====Regeln====&lt;br /&gt;
Es gibt vertikale Kanten(parent.level == child.level + 1 ) und horizontale Kanten(parent.level == child.level)&lt;br /&gt;
:*verfeinerter Regel :  ''level kodiert reduzierten Abstand von Setinel''  (d.b. horizontale Kanten werden nicht gezählt)&lt;br /&gt;
::(die nächste zwei Regeln sichern die Balance):&lt;br /&gt;
::*alle RS-Pfade haben die gleiche reduzierte Länge oder ''root'' hat bei allen Pfaden das gleiche Level&lt;br /&gt;
::*kein Pfad hat 2 aufeinanderer folgende horizontale Kanten&lt;br /&gt;
:*nur Kanten zum rechten Kind dürfen horizontal sein&lt;br /&gt;
:*die reduzierte Höhe jedes Blatts ist '''hr=1'''&lt;br /&gt;
&lt;br /&gt;
====Beweis====&lt;br /&gt;
Vereinfachung des Algorithmus&lt;br /&gt;
:'''Satz''':ein Anderson-Baum ist balanciert.&lt;br /&gt;
*1. Sei '''hr'''- die reduzierte Höhe&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; jeder Teilbaum enthält mindestens &amp;lt;math&amp;gt;N\ge 2^{hr} -1 &amp;lt;/math&amp;gt;  Knoten&lt;br /&gt;
&lt;br /&gt;
::a). Blätter : reduzierte Höhe 1 =&amp;gt; &amp;lt;math&amp;gt; N\ge 2^{1} - 1 = 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
::b). inneren Knoten: jeder Unterbaum hat mindestens reduzierte Höhe &amp;lt;math&amp;gt; ~hr - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt;  jeder Unterbaum hat mindestens &amp;lt;math&amp;gt; ~2^{hr} -1 &amp;lt;/math&amp;gt; Knoten&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt;N \ge 2 (2^{hr-1} -1)+1 = 2^w - 2 + 1 = 2^{hr} -1&amp;lt;/math&amp;gt;&lt;br /&gt;
::::alle RS-Pfade heben gleiche Länge&lt;br /&gt;
[[Image:Anderson.png|700px|right]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
*2. Kein Pfad hat 2 aufeinanderfolgende horizontal Kanten&lt;br /&gt;
:::&amp;lt;math&amp;gt;\Rightarrow  d \le 2 hr + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
*3. zusammen:&lt;br /&gt;
:&amp;lt;math&amp;gt;N \ge 2^{\frac {d}{2}} - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;log_{2}{ N}+1\ge log 2^{\frac {d}{2}} - 1 \ge \frac {d}{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; ~d &amp;lt; 2 log_{2}{(N + 1)}&amp;lt;/math&amp;gt;     - balancierter Baum&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
:'''Suchzeit''' : &amp;lt;math&amp;gt; ~ f(N) = \mathcal{O}(log{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Wie erreicht man die Balance===&lt;br /&gt;
====Rotation====&lt;br /&gt;
[[Image:Abbildung4.jpg|text-top|500px|Illustration von Rotation]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
   def rotateRight (node):&lt;br /&gt;
      root = node.left&lt;br /&gt;
      node.left = node.right&lt;br /&gt;
      root.right = node&lt;br /&gt;
      return root&lt;br /&gt;
   def rotateLeft(node):&lt;br /&gt;
      root = node.right&lt;br /&gt;
      node.right = node.left&lt;br /&gt;
      root.left = node&lt;br /&gt;
      return root&lt;br /&gt;
&lt;br /&gt;
====Optimierung der Balance====&lt;br /&gt;
   &lt;br /&gt;
&lt;br /&gt;
       if node.left is not sentinel and node.level==node.left.level:&lt;br /&gt;
            node &amp;gt; rotateRight(node)&lt;br /&gt;
       if node.right is not sentinel and node.right.right is  not sentinel and node.level==rotate.right.right.level:&lt;br /&gt;
            node = rotateLeft(node)&lt;br /&gt;
            node.level += 1&lt;/div&gt;</summary>
		<author><name>Nata</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1484</id>
		<title>Suchen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Suchen&amp;diff=1484"/>
				<updated>2008-05-29T00:09:54Z</updated>
		
		<summary type="html">&lt;p&gt;Nata: /* Rotation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Es wäre super wenn jemand ganz kurz schreiben könnte, was am Donnerstag zu den Funktionen treeInsert/Remove/HasKey gemacht wurde, was man ja für den aktuellen Übungszettel braucht. Danke :-)&amp;lt;br /&amp;gt;&lt;br /&gt;
/edit: an eine Funktion &amp;quot;HasKey&amp;quot; kann ich mich aus der Vorlesung nicht erinnern. Wenn die jemand hat, bitte eintragen. - Protokollant&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--Hallo, ich kenne leider deinen Namen nicht, ich habe den Wiki-Eintrag von letztem Donnerstag gemacht.Da das Thema 'Dynamisches Array' noch Thema von letzter Woche war, hab ich die Wiederholung der amortisierten Kosten von diesem Mittwoch noch bei Effizienz eingetragen, du musst es also nicht noch mal in deinem Wiki eintragen. Du kannst dir aber gerne mal anschauen was ich geschrieben habe, vielleicht fallen dir noch ein paar Sachen ein, die man hätte besser machen können. lg, Franziska.--&amp;gt;&lt;br /&gt;
Das Suchen ist eine grundlegende Operation in der Informatik. Viele Probleme in der Informatik können auf Suchaufgaben zurückgeführt werden.&lt;br /&gt;
&lt;br /&gt;
Gemeint ist mit Suchen das Wiederauffinden einer oder mehrerer Datensätze aus einer Menge von früher gespeicherten Datensätzen. Ein paar einleitende Worte zum Suchproblem findet man [http://de.wikipedia.org/wiki/Suche hier].&lt;br /&gt;
&lt;br /&gt;
== Überblick verschiedener Suchmethoden ==&lt;br /&gt;
&lt;br /&gt;
Um sich der Vielseitigkeit von Suchproblemen bewusst zu werden, ist es sinnvoll, sich einen Überblick über verschiedene Suchmethoden zu verschaffen. &lt;br /&gt;
&lt;br /&gt;
Hier sei auch auf einen bereits existierenden Wikipedia-Artikel zu [http://de.wikipedia.org/wiki/Suchverfahren Suchverfahren] verwiesen.&lt;br /&gt;
&lt;br /&gt;
Allen gemeinsam ist die grundlegende Aufgabe, ein Datenelement mit bestimmten Eigenschaften aus einer großen Menge von Datenelementen zu selektieren.&lt;br /&gt;
Dies kann, natürlich ohne jeden Anspruch auf Vollständigkeit, nach einer der jetzt diskutierten Methoden geschehen:&lt;br /&gt;
&lt;br /&gt;
* '''Schlüsselsuche''': meint das Suchen von Elementen mit bestimmtem Schlüssel; ein klassisches Beispiel wäre das Suchen in einem Wörterbuch,  die Schlüssel entsprechen hier den Wörtern, die Datensätze wären die zu den Wörtern gehörigen Eintragungen.&lt;br /&gt;
&lt;br /&gt;
* '''Bereichssuche''': Im Allgemeinen meint die Bereichssuche in n-Dimensionen die Selektion von Elementen mit Eigenschaften aus einem bestimmten n-dimensionalen Volumen. Im eindimensionalen Fall will man alle Elemente finden, deren Eigenschaft(en) in einem bestimmten Intervall liegen. Die Verallgemeinerung auf n-Dimensionen ist offensichtlich. Ein Beispiel für die Bereichssuche in einer 3D-Kugel wäre ein Handy mit Geolokalisierung, welches alle Restaurants in einem Umkreis von 500m findet. Lineare Ungleichungen werden graphisch durch [http://de.wikipedia.org/wiki/Hyperebene Hyperebenen] repräsentiert. In 2D sind diese Hyperebenen Geraden. Die Ungleichungen können dann den Lösungsraum in irgendeiner Form begrenzen.&lt;br /&gt;
&lt;br /&gt;
* '''Ähnlichkeitssuche''': Finde Elemente, die gegebenen Eigenschaften möglichst ähnlich sind. Ein prominentes Beispiel ist Google (=Ähnlichkeit zwischen Suchbegriffen und Dokumenten) oder das Suchen des nächstengelegenen Restaurants (Ähnlichkeit zwischen eigener Position und Position des Restaurants). Ein wichtigster Spezialfall ist die ''nächste-nachbar Suche''.&lt;br /&gt;
&lt;br /&gt;
* '''Graphensuche''': Hier wäre beispielsweise das Problem optimaler Wege zu nennen (Navigationssuche). Dieser Punkt wird später im Verlauf der Vorlesung noch einmal aufgegriffen werden.&lt;br /&gt;
&lt;br /&gt;
Im jetzt Folgenden wird nur noch die ''Schlüsselsuche'' betrachtet werden.&lt;br /&gt;
&lt;br /&gt;
==Sequentielle Suche==&lt;br /&gt;
&lt;br /&gt;
Die ''sequentielle'' oder ''lineare'' Suche ist die 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 = sequentialSearch(a, key) &lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq &amp;lt;/math&amp;gt; foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def sequentialSearch(a, key):&lt;br /&gt;
    for i in range(len(a)):&lt;br /&gt;
        if a[i] == key:  # bzw. allgemeiner a[i].key == key &lt;br /&gt;
            return i&lt;br /&gt;
    return -1&lt;br /&gt;
&lt;br /&gt;
Wir wollen jetzt die Komplexität dieses Algorithmus bestimmen, wobei die Problemgröße durch &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt; gegeben ist. &lt;br /&gt;
&lt;br /&gt;
Dabei nimmt man an, dass der innerste Vergleich (a[i] == key) jeweils &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt; ist (diese Annahme könnte verletzt sein, wenn der Vergleichsoperator überladen ist und dadurch eine höhere Komplexität hat). Dieser Vergleich wird in der for-Schleife jeweils N-mal durchgeführt (&amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt;), so dass man nach der Verschachtelungsregel eine gesamte Komplexität von &amp;lt;math&amp;gt; \mathcal{O}(N)&amp;lt;/math&amp;gt; erhält.&lt;br /&gt;
&lt;br /&gt;
Der Name ''lineare'' Suche rührt von diesem linearen Anwachsen der Komplexität mit der Arraygröße her.&lt;br /&gt;
&lt;br /&gt;
==Binäre Suche==&lt;br /&gt;
&lt;br /&gt;
Wie wir weiter unten zeigen werden, gestattet es diese Suchmethode, die Gesamtdauer der Suche in großen Datensätzen beträchtlich zu verringern. Die Methode beruht auf dem [http://de.wikipedia.org/wiki/Divide_and_Conquer Divide and Conquer-Prinzip], wobei die Suche in jedem Schritt rekursiv auf eine Hälfte des Datensatzes eingeschränkt wird. Weitere Details zur Methode sind [http://de.wikipedia.org/wiki/Bin%C3%A4re_Suche hier] zu finden. &lt;br /&gt;
&lt;br /&gt;
Die Methode ist nur dann anwendbar beziehungsweise effektiv, wenn folgendes gilt:&lt;br /&gt;
&lt;br /&gt;
# Auf der Eigenschaft der Daten, die zur Suche verwendet wird, ist eine Ordnung im Sinne von &amp;lt; oder &amp;gt; definiert.&lt;br /&gt;
# Wir wollen uns auf Datensätze beschränken, die schon fertig aufgebaut sind, in die also keine neuen Elemente mehr eingefügt werden, wenn man mit dem Suchen beginnt. Ist dies nicht der Fall, so ist unter Umständen die Implementierung über einen [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum Binärbaum] (siehe auch weiter unten) geschickter. &lt;br /&gt;
&lt;br /&gt;
Der folgende Algorithmus zeigt eine beispielhafte Implementierung der Methode (wir setzen wieder &amp;lt;tt&amp;gt;N = len(a)&amp;lt;/tt&amp;gt;):&lt;br /&gt;
&lt;br /&gt;
 a = [...,...]     # array&lt;br /&gt;
 a.sort()   # sortiere über Ordnung des Schlüssels&lt;br /&gt;
 foundIndex = binSearch(a, key, 0, len(a))  # (Array, Schlüssel, von wo bis wo suchen im Array)&lt;br /&gt;
 # foundIndex == -1 wenn nichts gefunden, 0 &amp;lt;math&amp;gt;\leq&amp;lt;/math&amp;gt;  foundIndex &amp;lt; len(a) wenn key gefunden (erster Eintrag mit diesem Wert)&lt;br /&gt;
&lt;br /&gt;
 def binSearch(a, key, start, end):  # start ist 1. Index, end ist letzter Index + 1&lt;br /&gt;
    size = end - start   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if size &amp;lt;= 0:   # Bereich leer?  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return -1   # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    center = (start + end)/2   # Integer Division, Ergebnis wird abgerundet, wichtig für ganzzahlige Indizes &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    if a[center] == key:  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return center  # &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
    elif a[center] &amp;lt; key:  &amp;lt;math&amp;gt; \mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
        return binSearch(a, key, center + 1, end)  # Rekursion&lt;br /&gt;
    else:&lt;br /&gt;
        return binSearch(a, key, start + 1, center)  # Rekursion&lt;br /&gt;
&lt;br /&gt;
Zur Berechnung der Komplexität dieses Algorithmus vernachlässigen wir zunächst den Aufwand, den die Sortierung weiter oben verursacht. Dieser Schritt mag oder mag nicht zulässig sein.&lt;br /&gt;
&lt;br /&gt;
Nach der Sequenzregel haben auch alle &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen die Komplexität &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;.  Es bleibt die Komplexität der Rekursion zu berechnen. Die gesamte Komplexität des Algorithmus (jetzt als Funktion f bezeichnet) setzt sich zusammen aus den oben erwähnten &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt; Anweisungen sowie der Rekursion und ist &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f(N) = \mathcal{O}(1) + f(N/2) = \mathcal{O}(1) + \mathcal{O}(1) + f(N/4) = ... = \underbrace{\mathcal{O}(1) + ... + \mathcal{O}(1) + \underbrace{f(0)}_{\mathcal{O}(1)\, \rightarrow \,\mathrm{size-Abfrage}}}_{n+1 \,\mathrm{Terme}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Falls jetzt gilt &amp;lt;math&amp;gt; N = 2^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \rightarrow f(N) = \mathcal{O}(1) \cdot \mathcal{O}(n+1) = \mathcal{O}(n) = \mathcal{O}(\lg N) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Für große Datenmengen ist die ''binäre Suche'' also weit effizienter als die ''lineare Suche''. Verdoppelt sich beispielsweise die zu durchsuchende Datenmenge, so verdoppelt sich der Aufwand für die ''sequentielle Suche'' - bei der ''binären Suche'' hingegen benötigt man lediglich eine zusätzliche Vergleichsoperation. &lt;br /&gt;
&lt;br /&gt;
Für kleine Daten (&amp;lt;math&amp;gt; N = 4,\, 5 &amp;lt;/math&amp;gt;) ist die ''sequentielle Suche'' jedoch schneller als die ''binäre Suche'', da hier die rekursiven Funktionsaufrufe teurer als das Mehr an Vergleichen sind. Ein anderer ungünstiger Fall ist gegeben, wenn nur sehr wenige Suchanfragen erfolgen (weniger als &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; viele). Dann wird der Aufwand durch das Sortieren des Arrays dominiert, ist also &amp;lt;math&amp;gt;\mathcal{O}(N \lg N) &amp;lt;/math&amp;gt;. Auch dann ist sequentielle Suche vorzuziehen.&lt;br /&gt;
&lt;br /&gt;
Eine relativ einfache Möglichkeit, die ''binäre Suche'' zu verbessern, ist die sogenannte ''Interpolationssuche''. Hierbei wird die neue Position für die Suche, also die Mitte des Arrays, durch eine Schätzung ersetzt, die angibt, wo sich der Schlüssel innerhalb des Arrays befinden könnte. Bei der Suche in einem Telefonbuch nach dem Namen Zebra würde man ja auch nicht in der Mitte anfangen. Näheres hierzu im Buch von ''Sedgewick''.&lt;br /&gt;
&lt;br /&gt;
Um sich den Algorithmus der ''binären Suche'' klar zu machen, ist es instruktiv, sich die folgende Tabelle genauer anzusehen, die die sukzessive Belegung der Variablen bei verschiedenen Anfragen beschreibt. Die Testfälle wurden nach dem Prinzip des ''domain partitioning'' gewählt. Das zugehörige Array hat die Einträge&lt;br /&gt;
&lt;br /&gt;
 a = [2, 3, 4, 5, 6]&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align:center&amp;quot; border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;5&amp;quot; cellspacing=&amp;quot;0&amp;quot; &lt;br /&gt;
! gesuchter key   !!  start      !! end  !! size !! center !! return &amp;lt;br/&amp;gt; (-1 oder index)  !! Kommentare  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 4     ||0            || 5    ||  5   || 2   ||  2         || gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 2     || 0           || 5    ||  5   || 2   ||            || linker Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||           &lt;br /&gt;
|-&lt;br /&gt;
|       ||  0          || 1    ||  1   || 0   ||  0         || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 1     ||0            || 5    ||  5   || 2   ||            || links außerhalb&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 2    ||  2   || 1   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 1    ||  1   || 0   ||            ||&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||0            || 0    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|-&lt;br /&gt;
| 6     ||0            || 5    ||  5   || 2   ||            || rechter Randfall&lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    || 2    || 4   || 4          || gefunden&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
| 5     ||0            || 5    ||  5   || 2   ||            || typischer Fall&lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       ||3            || 5    ||  2   || 4   ||            ||  &lt;br /&gt;
|- bgcolor=&amp;quot;#e0e0e0&amp;quot;&lt;br /&gt;
|       || 3           || 4    ||  1   || 3   || 3          || gefunden&lt;br /&gt;
|- &lt;br /&gt;
| 7     ||0            || 5    ||  5   || 2   ||            || rechts außerhalb        &lt;br /&gt;
|-&lt;br /&gt;
|       ||  3          || 5    ||  2   || 4   ||            ||&lt;br /&gt;
|-&lt;br /&gt;
|       ||5            || 5    ||  0   ||     || -1         || nichts gefunden&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Suche in einem Binärbaum ==&lt;br /&gt;
&lt;br /&gt;
Eine kurze Einführung in Binärbäume findet man [http://de.wikipedia.org/wiki/Bin%C3%A4rbaum hier].&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum.png|text-top|300x300px|Zur Illustration von Bäumen]]&lt;br /&gt;
&lt;br /&gt;
Bäume sind zweidimensional verkettete Strukturen. Sie gehören zu den fundamentalen Datenstrukturen in der Informatik. Da man in Bäumen nicht nur Daten speichern kann, sondern auch relevante Beziehungen der Daten untereinander, festgelegt über eine Ordnung auf der vergleichenden Dateneigenschaft (''Schlüssel''), eignen sich Bäume also insbesondere, um gesuchte Daten schnell wieder auffinden zu können.&lt;br /&gt;
&lt;br /&gt;
Ein ''Binärbaum'' wie oben skizziert besteht aus einer Menge von ''Knoten'', die untereinander durch ''Kanten'' verbunden sind. Jeder Knoten hat einen linken und einen rechten Unterbaum, der auch leer sein kann (in Python ließe sich dies mit ''None'' implementieren). Führt eine Kante von Knoten A zu Knoten B, so heißt A Vater von B und B Kind von A. Es gibt genau einen Knoten ohne Vater, den man ''Wurzel'' nennt. Knoten ohne Kinder heißen ''Blätter''.&lt;br /&gt;
&lt;br /&gt;
Ein ''Suchbaum'' hat zusätzlich die Eigenschaft, dass die Schlüssel jedes Knotens sortiert sind. Alle Schlüssel im linken Unterbaum sind kleiner, alle Schlüssel im rechten Unterbaum sind größer als ihr Vater. Wir wollen hierbei annehmen, dass jeder Schlüssel pro Datensatz nur einmal vorkommt, da sich sonst die &amp;gt;- oder &amp;lt;-Relation nicht mehr strikt erfüllen ließe.&lt;br /&gt;
&lt;br /&gt;
Um in einem Baum suchen zu können, wollen wir von zwei Annahmen ausgehen:&lt;br /&gt;
# Einfügen und Suchen im Baum wechseln sich ab. (Wenn das Suchen erst beginnt, nachdem alle Einfügungen erfolgt sind, wäre ein dynamisches Array mit binärer Suche wie oben wesentlich einfacher.)&lt;br /&gt;
# Der Schlüssel, der die Anordnung bestimmt, kennt eine [http://de.wikipedia.org/wiki/Ordnungsrelation Ordnung] (&amp;lt;-Relation oder &amp;gt;-Relation).&lt;br /&gt;
&lt;br /&gt;
Der folgende Python-Code zeigt beispielhaft, wie man in einem Suchbaum suchen könnte. Der Konstruktor für einen Knoten des Suchbaums ließe sich zum Beispiel so implementieren:&lt;br /&gt;
 &lt;br /&gt;
 class Node:&lt;br /&gt;
     def __init__(self, key):&lt;br /&gt;
         self.key = key&lt;br /&gt;
         self.left = self.right = None&lt;br /&gt;
 &lt;br /&gt;
 root = ...    # Wurzel des Suchbaums&lt;br /&gt;
 nodeFound = treeSearch(root, key)   # None, falls nichts gefunden&lt;br /&gt;
 &lt;br /&gt;
 def treeSearch(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return None&lt;br /&gt;
     elif node.key == key:&lt;br /&gt;
         return node&lt;br /&gt;
     elif key &amp;lt; node.key:&lt;br /&gt;
         return treeSearch(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         return treeSearch(node.right, key)&lt;br /&gt;
&lt;br /&gt;
Daraus resultiert der folgende Suchalgorithmus:&lt;br /&gt;
&lt;br /&gt;
 def treeSort(node,array):     # dynamisches Array als 2. Argument&lt;br /&gt;
     if node is None:     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
         return None:&lt;br /&gt;
     treeSort(node.left, array)     # rekursiv&lt;br /&gt;
     array.append(node.key)     # &amp;lt;math&amp;gt;\mathcal{O}(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
     treeSort(node.right, array)     # rekursiv&lt;br /&gt;
&lt;br /&gt;
Komplexität:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 f(N)=\mathcal{O}(1)+f(N_\mathrm{left})+f(N_\mathrm{right})=\mathcal{O}(1)+\mathcal{O}(1)+f(N_\mathrm{leftleft})+f(N_\mathrm{leftright})+\mathcal{O}(1)+f(N_\mathrm{rightleft})&lt;br /&gt;
 +f(N_\mathrm{leftright})=N\ast\mathcal{O}(1)=\mathcal{O}(N)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sortier-Pseudocode:&lt;br /&gt;
&lt;br /&gt;
 Sortieren:&lt;br /&gt;
     (Array) a     # unsortiert&lt;br /&gt;
     (tree) t     # zunächst leer&lt;br /&gt;
 (dynamisches Array) r     # später sortiert&lt;br /&gt;
 for e in a:&lt;br /&gt;
     t = treeInsert(t, e)&lt;br /&gt;
 treeSort(t, r)&lt;br /&gt;
&lt;br /&gt;
== Insert ==&lt;br /&gt;
&lt;br /&gt;
Was passiert wenn der key (Schlüssel) schon vorhanden ist?&lt;br /&gt;
* error (exception)&lt;br /&gt;
* nichts einfügen&lt;br /&gt;
* nichts einfügen aber einen boolean return zurückgeben (einfügen=true, false)&lt;br /&gt;
* nochmals eingefügt (z.B. in der Node Klasse)&lt;br /&gt;
&lt;br /&gt;
Wobei die ersten 3 Punkte zur Mengensemantik gehören und der letzte eine Multimenge ist.&lt;br /&gt;
&lt;br /&gt;
Algorithmus im zweiten Fall (nichts einfügen):&lt;br /&gt;
&lt;br /&gt;
 def treeInsert(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return Node(key)     # Alternative Schreibweise: node = Node(key)&lt;br /&gt;
     if node.key == key:      # und dann:&lt;br /&gt;
         return node          # pass&lt;br /&gt;
     elif key &amp;lt; node.key:     # links im Baum&lt;br /&gt;
         node.left = treeInsert(node.left, key)&lt;br /&gt;
     else:&lt;br /&gt;
         node.right = treeInsert(node.right, key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Remove ==&lt;br /&gt;
Fälle:&lt;br /&gt;
# key (bzw. Knoten der key enthält) ist ein Blatt =&amp;gt; einfach löschen&lt;br /&gt;
# node hat &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; linken Unterbaum oder &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; rechten Unterbaum =&amp;gt; durch Unterbaum ersetzen&lt;br /&gt;
# node hat beide Unterbäume:&lt;br /&gt;
#* Suche Vorgänger: max k &amp;lt; key (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; keys); Vorgänger ist immer ein Fall 1 oder Fall 2&lt;br /&gt;
#*:=&amp;gt; ersetze node durch Vorgänger und entferne Vorgänger&lt;br /&gt;
&lt;br /&gt;
 def treePredecessor(node):     # wird nur bei Fall 3 aufgerufen&lt;br /&gt;
     node = node.left&lt;br /&gt;
     while node.right is not None:&lt;br /&gt;
         node = node.right&lt;br /&gt;
     return node&lt;br /&gt;
 &lt;br /&gt;
 def treeRemove(node, key):&lt;br /&gt;
     if node is None:&lt;br /&gt;
         return node&lt;br /&gt;
     if key &amp;lt; node.key:&lt;br /&gt;
         node.left = treeRemove(node.left, key)&lt;br /&gt;
     elif key &amp;gt; node.key:&lt;br /&gt;
         node.right = treeRemove(node.right, key)&lt;br /&gt;
     else:&lt;br /&gt;
         if node.left is None and node.right is None:     # Fall 1&lt;br /&gt;
             node = None&lt;br /&gt;
         elif node.left is None:     # Fall 2&lt;br /&gt;
             node = node.right       # +&lt;br /&gt;
         elif node.right is None:    # Fall 2&lt;br /&gt;
             node = node.left&lt;br /&gt;
         else:&lt;br /&gt;
             pred = treePredecessor(node)&lt;br /&gt;
             node.key = pred.key&lt;br /&gt;
             node.left = treeRemove(node.left, pred.key)&lt;br /&gt;
     return node&lt;br /&gt;
&lt;br /&gt;
== Komplexitätsanalyse ==&lt;br /&gt;
&lt;br /&gt;
* Pfad (Zwischen node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;): &lt;br /&gt;
**Folge von Knoten (node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt;,...,node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt;), sodass:&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;k1&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;kn&amp;lt;/sub&amp;gt; == node&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;&lt;br /&gt;
*** node&amp;lt;sub&amp;gt;ki&amp;lt;/sub&amp;gt; und node&amp;lt;sub&amp;gt;ki+1&amp;lt;/sub&amp;gt; haben eine gemeinsame Kante.&lt;br /&gt;
[[Image:Baum_Pfad.png]]&lt;br /&gt;
(Ein Baum ist ein Graph, indem es zwischen beliebigen Knoten stets genau einen Pfad gibt.)&lt;br /&gt;
&lt;br /&gt;
* Länge eines Pfades: Anzahl der Kanten = Anzahl der Knoten - 1&lt;br /&gt;
* Tiefe eines Knotens: Pfadlänge vom Knoten zur Wurzel des Baumes.&lt;br /&gt;
* Tiefe des Baumes: maximale Tiefe eines Knotens&lt;br /&gt;
* Balance eines Baumes: maximale Tiefe(k) - minimale Tiefe(k) (k &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; Blätter)&lt;br /&gt;
&lt;br /&gt;
== Ungünstigster Fall von treeSearch ==&lt;br /&gt;
&lt;br /&gt;
Komplexität von treeSearch = Länge des Pfades zum Knoten wo &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; gefunden wird, oder erkannt wird, dass &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; nicht im Baum ist.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;tt&amp;gt;key&amp;lt;/tt&amp;gt; wird nicht gefunden, aber für diese Entscheidung muss der Längste Pfad vollständig durchlaufen werden.&amp;lt;br /&amp;gt;&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(T)&amp;lt;/math&amp;gt; wo T = Tiefe des Baumes&lt;br /&gt;
*: [1,2,3,4,5]:&amp;lt;br /&amp;gt; [[Image:Balance.png]]&lt;br /&gt;
*: Tiefe T = 4, Balance = 4&lt;br /&gt;
=&amp;gt; Ungünstigster Fall: &amp;lt;math&amp;gt;\mathcal{O}(N)&amp;lt;/math&amp;gt; wo N = Anzahl der Elemente.&lt;br /&gt;
&lt;br /&gt;
== Aufgabe ==&lt;br /&gt;
&lt;br /&gt;
Minimiere Balance (erzeuge balancierten Baum):&lt;br /&gt;
# Einfügen in geschickter Reihenfolge (siehe Übungsaufgabe)&lt;br /&gt;
# Selbstbalancierter Baum:&lt;br /&gt;
#* Überprüfen der Balance nach jedem Einfügen&lt;br /&gt;
#* Umstrukturieren des Baumes, falls Balance &amp;gt; 1 (Suchbaum-Bedingung muss erhalten bleiben)&lt;br /&gt;
#* AVL-Bäume (älteste Variante)&lt;br /&gt;
#* Rot-Schwarz-Bäume (verbreiteste Variante)&lt;br /&gt;
#* Treaps (flexibelste Variante, siehe Übung)&lt;br /&gt;
#* Splay trees&lt;br /&gt;
#* Andersson Trees (einfachste Variante)&lt;br /&gt;
(#* Skip Lists (schnellste Variante, aber kein Binärbaum))&lt;br /&gt;
&lt;br /&gt;
== Umstrukturieren, so dass Suchbaumbedingung erhalten bleibt: ==&lt;br /&gt;
&lt;br /&gt;
Rotation: elementare Umstrukturierungen&lt;br /&gt;
&lt;br /&gt;
[[Image:Baum_Rotation.png]]&lt;br /&gt;
&lt;br /&gt;
 def rotateRight(node):&lt;br /&gt;
     newRoot = node.left&lt;br /&gt;
     node.left = newRoot.right&lt;br /&gt;
     newRoot.right = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
 def rotateLeft(node):&lt;br /&gt;
     newRoot = node.right&lt;br /&gt;
     node.right = newRoot.left&lt;br /&gt;
     newRoot.left = node&lt;br /&gt;
     return newRoot&lt;br /&gt;
&lt;br /&gt;
== Balance eines Suchbaumes ==&lt;br /&gt;
&lt;br /&gt;
===Balance eines Baumes zu  definieren:===&lt;br /&gt;
[[Image:Abbildung1.jpg|250px|right]]&lt;br /&gt;
*betrachte &amp;lt;tt&amp;gt;None&amp;lt;/tt&amp;gt; als zusätzlichen Knoten : '''sentinel'''   ([[englische Sprache|engl.]] für ''Wächter'')&lt;br /&gt;
*oder defeniere speziellen sentinel-Knoten&lt;br /&gt;
*RS - Pfades : von &amp;lt;tt&amp;gt;root&amp;lt;/tt&amp;gt; &amp;amp;rarr; ''sentinel''&lt;br /&gt;
*Länge eines Pfades ''P''      '''''|P|'''''&lt;br /&gt;
* &amp;lt;u&amp;gt;die Balance eines Baumes zu defenieren:&amp;lt;/u&amp;gt;&lt;br /&gt;
: '''''B = max P '''''&amp;lt;math&amp;gt; \in &amp;lt;/math&amp;gt; '''''{RS}    |P| - min |P|'''''&lt;br /&gt;
:'''''{RS}'''''  Menge aller RS-Pfade&lt;br /&gt;
;vollständiger Baum:  Balance &amp;lt;math&amp;gt;~=0&amp;lt;/math&amp;gt;&lt;br /&gt;
:Alle Knoten(außer Blättern) haben 2 Kinder.&lt;br /&gt;
;perfect balancierter Baum:  Balance  &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 1&lt;br /&gt;
'''alternative Defenition für perfect balancierte Bäume'''&lt;br /&gt;
:Für jeden Teilbaum gilt es: rechtes und linkes Kind (für jeden Knoten) sind auch wieder perfect balancierte Bäume und ihre Höhe unterscheidet sich höchstens um '''1'''.&lt;br /&gt;
&lt;br /&gt;
===Größe===&lt;br /&gt;
[[Image:baum-v.png|700px|right]]&lt;br /&gt;
====Größe des vollständigen Baumes====&lt;br /&gt;
Ebene K hat 2 &amp;lt;sup&amp;gt;k&amp;lt;/sup&amp;gt; Knoten.&lt;br /&gt;
Falls Tiefe = d , dann N = 2&amp;lt;sup&amp;gt;0&amp;lt;/sup&amp;gt; + 2&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;.....+ 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; = 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1&lt;br /&gt;
&lt;br /&gt;
====Größe des perfect balancierten Baumes====&lt;br /&gt;
Die Tiefe d, kann nicht besser sein als vollständiger Baum&lt;br /&gt;
:'''=&amp;gt;'''N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2 &amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
;schlechteste perfect balancierter Baum : ist ein vollständiger der Tiefe  d - 1 + 1, N &amp;lt;math&amp;gt; \ge&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:'''=&amp;gt;'''  2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; N &amp;lt;math&amp;gt; \le&amp;lt;/math&amp;gt; 2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; - 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;2&amp;lt;sup&amp;gt;d&amp;lt;/sup&amp;gt; &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt; -1)   &amp;lt;math&amp;gt;~&amp;lt;&amp;lt;/math&amp;gt; log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;d+1&amp;lt;/sup&amp;gt;)&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
d &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt;  log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; N &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; (d + 1)&lt;br /&gt;
&lt;br /&gt;
*Ergibt die Komplexität der Suche im schlechtesten Fall: Anzahl der Vergleiche pro Knoten( = 2 bzw. = 3)&amp;lt;math&amp;gt;\ast&amp;lt;/math&amp;gt;Anzahl der Knoten&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow ~f(N)\le 2d \le 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
*'''perfekt balancierter Baum '''=&amp;gt; AVL-Baum &lt;br /&gt;
*'''balancierter Baum''':&lt;br /&gt;
::&amp;lt;math&amp;gt;\forall  N:d(N)\le c \ast d (N)&amp;lt;/math&amp;gt; und  &amp;lt;math&amp;gt;1 \le c &amp;lt; \mathcal {1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:: d ist die Tiefe von perfekt balancierten Baum&lt;br /&gt;
&lt;br /&gt;
:''Komplexität der Suche'':&amp;lt;math&amp;gt; ~f(N)\le  c\ast 2\log_{2}{ N} = \mathcal{O}(\log_{2}{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
:algorithmisch einfacher als perfekt balancierter Baum, aber fast genauso schnell&lt;br /&gt;
&lt;br /&gt;
====Selbst-balancierter Baum====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  def insertTree(node,key):&lt;br /&gt;
    if node is sentinel:       #(None = sentinel)&lt;br /&gt;
        return Node(key)       &lt;br /&gt;
    if node.key == key:&lt;br /&gt;
        return Node&lt;br /&gt;
    if key &amp;lt; node.key:&lt;br /&gt;
        Node = insertTree(node.left, key)&lt;br /&gt;
    else:&lt;br /&gt;
        node.right = insertTree(node.right, key)&lt;br /&gt;
   #optimiere Balance:&lt;br /&gt;
        return node&lt;br /&gt;
&lt;br /&gt;
===Anderson-Bäume===&lt;br /&gt;
&lt;br /&gt;
  class Node:&lt;br /&gt;
    def__init__(self, key):&lt;br /&gt;
        self.key = key&lt;br /&gt;
        self.left = selft.right = None&lt;br /&gt;
        #einfügen:&lt;br /&gt;
        self.level = 1&lt;br /&gt;
&lt;br /&gt;
:''level'' : kodiert Abstand von sentinel&lt;br /&gt;
====Regeln====&lt;br /&gt;
Es gibt vertikale Kanten(parent.level == child.level + 1 ) und horizontale Kanten(parent.level == child.level)&lt;br /&gt;
:*verfeinerter Regel :  ''level kodiert reduzierten Abstand von Setinel''  (d.b. horizontale Kanten werden nicht gezählt)&lt;br /&gt;
::(die nächste zwei Regeln sichern die Balance):&lt;br /&gt;
::*alle RS-Pfade haben die gleiche reduzierte Länge oder ''root'' hat bei allen Pfaden das gleiche Level&lt;br /&gt;
::*kein Pfad hat 2 aufeinanderer folgende horizontale Kanten&lt;br /&gt;
:*nur Kanten zum rechten Kind dürfen horizontal sein&lt;br /&gt;
:*die reduzierte Höhe jedes Blatts ist '''hr=1'''&lt;br /&gt;
&lt;br /&gt;
====Beweis====&lt;br /&gt;
Vereinfachung des Algorithmus&lt;br /&gt;
:'''Satz''':ein Anderson-Baum ist balanciert.&lt;br /&gt;
*1. Sei '''hr'''- die reduzierte Höhe&lt;br /&gt;
:&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; jeder Teilbaum enthält mindestens &amp;lt;math&amp;gt;N\ge 2^{hr} -1 &amp;lt;/math&amp;gt;  Knoten&lt;br /&gt;
&lt;br /&gt;
::a). Blätter : reduzierte Höhe 1 =&amp;gt; &amp;lt;math&amp;gt; N\ge 2^{1} - 1 = 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
::b). inneren Knoten: jeder Unterbaum hat mindestens reduzierte Höhe &amp;lt;math&amp;gt; ~hr - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt;  jeder Unterbaum hat mindestens &amp;lt;math&amp;gt; ~2^{hr} -1 &amp;lt;/math&amp;gt; Knoten&lt;br /&gt;
::::&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt;N \ge 2 (2^{hr-1} -1)+1 = 2^w - 2 + 1 = 2^{hr} -1&amp;lt;/math&amp;gt;&lt;br /&gt;
::::alle RS-Pfade heben gleiche Länge&lt;br /&gt;
[[Image:Anderson.png|700px|right]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
*2. Kein Pfad hat 2 aufeinanderfolgende horizontal Kanten&lt;br /&gt;
:::&amp;lt;math&amp;gt;\Rightarrow  d \le 2 hr + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
*3. zusammen:&lt;br /&gt;
:&amp;lt;math&amp;gt;N \ge 2^{\frac {d}{2}} - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;log_{2}{ N}+1\ge log 2^{\frac {d}{2}} - 1 \ge \frac {d}{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; ~d &amp;lt; 2 log_{2}{(N + 1)}&amp;lt;/math&amp;gt;     - balancierter Baum&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
:'''Suchzeit''' : &amp;lt;math&amp;gt; ~ f(N) = \mathcal{O}(log{N})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Wie erreicht man die Balance===&lt;br /&gt;
====Rotation====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Image:Abbildung4.jpg|text-top|500px|Illustration von Rotation]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
   def rotateRight (node):&lt;br /&gt;
      root = node.left&lt;br /&gt;
      node.left = node.right&lt;br /&gt;
      root.right = node&lt;br /&gt;
      return root&lt;br /&gt;
   def rotateLeft(node):&lt;br /&gt;
      root = node.right&lt;br /&gt;
      node.right = node.left&lt;br /&gt;
      root.left = node&lt;br /&gt;
      return root&lt;br /&gt;
&lt;br /&gt;
====Optimierung der Balance====&lt;br /&gt;
   &lt;br /&gt;
&lt;br /&gt;
       if node.left is not sentinel and node.level==node.left.level:&lt;br /&gt;
            node &amp;gt; rotateRight(node)&lt;br /&gt;
       if node.right is not sentinel and node.right.right is  not sentinel and node.level==rotate.right.right.level:&lt;br /&gt;
            node = rotateLeft(node)&lt;br /&gt;
            node.level += 1&lt;/div&gt;</summary>
		<author><name>Nata</name></author>	</entry>

	</feed>