• Schlüssel sind geordnet gespeichert. Für jeden Knoten gilt : – Schlüssel im linken Teilbaum von p sind kleiner als der Schlüssel von p – Schlüssel im rechten Teilbaum von p sind grösser als der Schlüssel von p Informatik I - Übung 12 Bäume und Rekursion Daniel Hentzen [email protected] 21. Mai 2014 • Aufwand bei der Suche nach einem Wert : – best case : 1 – worst case : log2 (#Blattknoten) 1 Bäume 1.1 Allgemein 1.2.1 Implementierung : als array • Bäume sind verallgemeinerte Listenstrukturen : Ein Element (node) hat eine endliche, begrenzte Anzahl von Nachfolgern Wir können die Elemente ebenenweise in einem Array abspeichern. Um die Positionen der Arrayeinträge im Baum zu bestimmen, benutzen wir folgende Beziehungen : • Jeder Knoten ausser dem Wurzelknoten (root) hat genau einen Vorgängerknoten (parent) parent(i) = • Eine Kante drückt die Beziehung zwischen unmittelbarem Vorgänger und Nachfolger aus • Der Grad eines Knoten ist die Zahl seiner unmittelbarer Nachfolger i−1 2 (1) lef tson(i) = 2 · i + 1 (2) rightson(i) = 2 · i + 2 (3) • Ein Blattknoten (leaf) ist ein Knoten ohne Nachfolger, also mit Grad 0. • Höhe eines Baumes: Maximaler Abstand (Anzahl Kanten) eines Blattes von der Wurzel Der obige Binärbaum ist ebenenweise im Array gespeichert : 27 0 10 1 32 2 3 3 Beispiel : • Parent von 12 : parent(4) = 4−1 = 1.5 → 1 → 10 2 • Linker Sohn von 10 : 1.2 Binärbäume parent(4) = 2 · 1 + 1 = 3 → 3 • Spezialfall : Grad = 2, das heisst jeder Knoten hat maximal 2 Nachfolger • Rechter Sohn von 10 : • Vollständiger Binärbaum: Alle Blätter haben die gleiche Tiefe → Höhe = log2 (#Blattknoten) parent(4) = 2 · 1 + 2 = 4 → 12 1 12 4 28 5 34 6 Beispiel : aufsteigend (rekursiv!) v o i d traverse ( Node ∗ p ) // S t a r t p u n k t u e b e r g e b e n { i f ( p != NULL ) //wenn k e i n B l a t t k n o t e n { traverse ( p−>left ) ; cout << p−>value << ” ” ; traverse ( p−>right ) ; } } 1.2.2 Implementierung : als struct s t r u c t element { i n t value ; s t r u c t element ∗ left ; // p o i n t e r a u f r e c h t e n N a c h f o l g e r s t r u c t element ∗ right ; // p o i n t e r a u f l i n k e n N a c h f o l g e r }; t y p e d e f s t r u c t element Node ; Node ∗ root = NULL ; // p o i n t e r a u f Wurzel 1.4 Suchen... 1.4.1 ...rekursiv (ungeordnet/geordnet) 1.3 Durchlaufordnungen Wir suchen nach i und übergeben den Wurzelknoten als Argument. Rückgabe ist true falls sich der Wert im Baum befindet, false falls nicht. Man kann die Binärbäume in verschiedenen Reihenfolgen durchlaufen. Achtung : Man geht rekursiv vor, das heisst auf die Teilbäume wendet man wieder die gleiche Reihenfolge an, bis man bei den Blattknoten ankommt. b o o l rec_search ( Node ∗ node , i n t i ) { i f ( node == NULL ) return f a l s e ; i f ( node−>value == i ) return true ; r e t u r n rec_search ( node−>left , i ) | | rec_search ( node−>right , i ) ; } 1.3.1 Hauptreihenfolge (“preorder”) Wurzel, linker Teilbaum, rechter Teilbaum Obiges Beispiel : 27, 10, 3, 12, 32, 28, 34 1.4.2 ...iterativ (Binärbaum geordnet!) 1.3.2 Nebenreihenfolge (“postorder”) linker Teilbaum, rechter Teilbaum, Wurzel Node ∗ next = root ; i n t search = 6 ; w h i l e ( next != NULL && next−>value != search ) { i f ( next != NULL && next−>value != search ) { i f ( next−>value < search ) next = next−>right ; else next = next−>left ; } } Obiges Beispiel : 3, 12, 10, 28, 34, 32, 27 1.3.3 Symmetrische Reihenfolge (“inorder”) linker Teilbaum, Wurzel, rechter Teilbaum Beim geordneten Binärbaum gibt dies die Elemente aufsteigend aus. (absteigend : rechter Teilbaum, Wurzel, linker Teilbaum) Obiges Beispiel : 3, 10, 12, 27, 28, 32, 34 2 1.6 Gesamten Baum löschen 1.5 Einfügen (in geordneten Binärbaum) Wir wollen einen Knoten mit dem Wert value in den geordneten Binärbaum einfügen (als Blattknoten). Dafür müssen wir zuerst die richtige Position im Baum finden. v o i d deleteTree ( Node ∗ node ) { i f ( node != NULL ) { deleteTree ( node−>right ) ; deleteTree ( node−>left ) ; d e l e t e node ; } } 2 Sortieralgorithmen Sortieralgortihmen sortieren eine Liste von Werten (arrays). Es gibt viele verschiedene Varianten, die mehr oder weniger effizient sind. v o i d insert ( Node ∗ node , i n t value ) { i f ( node−>value == value ) return ; i f ( value < node−>value ) { i f ( node−>left != NULL ) // k e i n B l a t t k n o t e n insert ( node−>left , value ) ; e l s e // B l a t t k n o t e n e r r e i c h t { Node ∗ newNode = new Node ; newNode−>left = NULL ; newNode−>right = NULL ; newNode−>value = value ; node−>left = newNode ; } } else { i f ( node−>right != NULL ) insert ( node−>right , value ) ; else { Node ∗ newNode = new Node ; newNode−>left = NULL ; newNode−>right = NULL ; newNode−>value = value ; node−>right = newNode ; } 2.1 Selection Sort Vorgehen : Das kleinste Element wird gesucht und mit dem ersten Element der Liste vertauscht. Der Algorithmus wird ohne die bereits geordneten Elemente wiederholt, solange bis man am Ende der Liste ankommt. Algorithmus : Die Liste wird als array übergeben. length ist die Anzahl Elemente in der Liste. v o i d selectionSort ( i n t ∗ array , i n t length ) { i n t i , j , min , minat ; f o r ( i =0; i<(length −1) ; i++) { minat = i ; // Index vom Minimum min = array [ i ] ; // Wert vom Minimum f o r ( j=i +1; j<length ; j++) // a k t u e l l e s Minimum v e r g l e i c h e n { i f ( min>array [ j ] ) // f a l l s n e u e s Minimum g e f u n d e n { minat = j ; // Index w e c h s e l n min = array [ j ] ; // Wert w e c h s e l n } } //Minimum mit 1 . Wert d e r u n g e o r d n e t e n L i s t e v e r t a u s c h e n i n t temp = array [ i ] ; array [ i ] = array [ minat ] ; } } 3 array [ j ] = array [ j − 1 ] ; array [ j −1] = tmp ; j−−; array [ minat ] = temp ; } } } } } 2.2 Bubble Sort Vorgehen : Sortieren durch wiederholtes Vergleichen und eventuelles Vertauschen benachbarter array-Elemente. Nach dem ersten Durchlauf ist das grösste Element an der richtigen Position. Algorithmus : 2.4 Merge Sort Die Liste wird als array übergeben. length ist die Anzahl Elemente in der Liste. Vorgehen : “Divide and conquer”: Mergesort zerlegt die zu sortierende Liste in kleinere Listen (solange bis die Sublisten nur noch 1 Element haben = sortiert), die jede für sich sortiert werden. Die sortierten kleinen Listen werden dann im Reissverschlussverfahren zu grösseren Listen zusammengefügt, bis wieder eine sortierte Gesamtliste erreicht ist. v o i d bubbleSort ( i n t ∗ array , i n t length ) { f o r ( i n t i=length −1; i >0; i−−) // o u t e r l o o p { f o r ( i n t j =1; j<=i ; j++) // i n n e r l o o p { i f ( array [ j −1] > array [ j ] ) { i n t t = array [ j − 1 ] ; array [ j −1] = array [ j ] ; array [ j ] = t ; } } } } Pseudo-Code : funktion mergesort ( liste ) ; falls ( Groesse von liste <= 1 ) dann antworte liste sonst halbiere die liste in linkeListe , rechteListe linkeListe = mergesort ( linkeListe ) rechteListe = mergesort ( rechteListe ) antworte merge ( linkeListe , rechteListe ) funktion merge ( linkeListe , rechteListe ) ; neueListe solange ( linkeListe und rechteListe nicht leer ) | falls ( erstes Element der linkeListe <= erstes Element der ←rechteListe ) | dann fuege erstes Element linkeListe in die neueListe ←hinten ein und entferne es aus linkeListe | sonst fuege erstes Element rechteListe in die neueListe ←hinten ein und entferne es aus rechteListe solange_ende solange ( linkeListe nicht leer ) | fuege erstes Element linkeListe in die neueListe hinten ein←und entferne es aus linkeListe solange_ende solange ( rechteListe nicht leer ) | fuege erstes Element rechteListe in die neueListe hinten ←ein und entferne es aus rechteListe solange_ende antworte neueListe 2.3 Insertion Sort Vorgehen : Ein Element nach dem anderen in der Liste wird betrachtet. Man sucht seine Position in der bereits sortierten Liste der vorhergehenden Elemente und fügt es ein. Die nachfolgenden Elemente müssen verschoben werden. Algorithmus : Die Liste wird als array übergeben. length ist die Anzahl Elemente in der Liste. v o i d insertionSort ( i n t ∗ array , i n t length ) { i n t i , j , tmp ; f o r ( i =1; i<length ; i++) { j=i ; w h i l e ( j>0 && array [ j−1]> array [ j ] ) { tmp = array [ j ] ; 4 2.5 Quick Sort Vorgehen : “Divide and conquer” : ein Element als Pivot wählen, alle Elemente kleiner als der Pivot vor dem Pivot, alle Elemente grösser als der Pivot nach dem Pivot. (Pointer i ganz links, pointer j ganz rechts, Durchlaufe array von links (i++) bis a[i]>=Pivotelement. Durchlaufe array von rechts (j–) bis a[j]<=Pivotelement. Vertausche Elemente i und j. )Nach dieser Partitionierung ist das Pivotelement an der richtigen Position in der sortierten Liste. Rekursiv die linke und rechte Subliste nach dem gleichen Prinzip partitionieren. 2.6 Effizienz Algorithm Selection Bubble Merge Quick Worst Case Best Case Average Case O(n2 ) O(n2 ) O(n2 ) O(n2 ) O(n) O(n2 ) O(n · log(n)) O(n · log(n)) O(n · log(n)) O(n2 )) O(n · log(n)) O(n · log(n)) 5